From 680c2162a7fc5b839214fe70be65f2a18a8af06b Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Wed, 21 Nov 2018 11:19:55 +0100 Subject: [PATCH 1/5] paypal: new redirects --- website/client/components/page.vue | 6 - website/client/main.js | 2 +- website/client/router/handleRedirect.js | 14 +++ website/client/{router.js => router/index.js} | 115 +++++++++--------- .../controllers/top-level/payments/paypal.js | 4 +- 5 files changed, 76 insertions(+), 65 deletions(-) delete mode 100644 website/client/components/page.vue create mode 100644 website/client/router/handleRedirect.js rename website/client/{router.js => router/index.js} (79%) diff --git a/website/client/components/page.vue b/website/client/components/page.vue deleted file mode 100644 index c39bc57819..0000000000 --- a/website/client/components/page.vue +++ /dev/null @@ -1,6 +0,0 @@ - \ No newline at end of file diff --git a/website/client/main.js b/website/client/main.js index 928d48bd98..f6b0701dd8 100644 --- a/website/client/main.js +++ b/website/client/main.js @@ -4,7 +4,7 @@ import { setup as setupAnalytics, } from 'client/libs/analytics'; import { setUpLogging } from 'client/libs/logging'; -import router from './router'; +import router from './router/index'; import getStore from './store'; import StoreModule from './libs/store'; import './filters/registerGlobals'; diff --git a/website/client/router/handleRedirect.js b/website/client/router/handleRedirect.js new file mode 100644 index 0000000000..dd5dee4fe6 --- /dev/null +++ b/website/client/router/handleRedirect.js @@ -0,0 +1,14 @@ +export default function (to, from, next) { + const redirect = to.params.redirect; + + switch (redirect) { + case 'paypal-success-checkout': + console.log('checkout'); + break; + case 'paypal-success-subscribe': + console.log('subscribe'); + break; + default: + next({name: 'notFound'}); + } +} \ No newline at end of file diff --git a/website/client/router.js b/website/client/router/index.js similarity index 79% rename from website/client/router.js rename to website/client/router/index.js index a7c53c18ff..2052ded759 100644 --- a/website/client/router.js +++ b/website/client/router/index.js @@ -2,51 +2,49 @@ import Vue from 'vue'; import VueRouter from 'vue-router'; import getStore from 'client/store'; import * as Analytics from 'client/libs/analytics'; +import handleRedirect from './handleRedirect'; -// import EmptyView from './components/emptyView'; - -// TODO Dummy elements used as placeholder until real components are implemented -import ParentPage from './components/parentPage'; +import ParentPage from 'client/components/parentPage'; // Static Pages -const StaticWrapper = () => import(/* webpackChunkName: "entry" */'./components/static/staticWrapper'); -const HomePage = () => import(/* webpackChunkName: "entry" */'./components/static/home'); +const StaticWrapper = () => import(/* webpackChunkName: "entry" */'client/components/static/staticWrapper'); +const HomePage = () => import(/* webpackChunkName: "entry" */'client/components/static/home'); -const AppPage = () => import(/* webpackChunkName: "static" */'./components/static/app'); -const ClearBrowserDataPage = () => import(/* webpackChunkName: "static" */'./components/static/clearBrowserData'); -const CommunityGuidelinesPage = () => import(/* webpackChunkName: "static" */'./components/static/communityGuidelines'); -const ContactPage = () => import(/* webpackChunkName: "static" */'./components/static/contact'); -const FAQPage = () => import(/* webpackChunkName: "static" */'./components/static/faq'); -const FeaturesPage = () => import(/* webpackChunkName: "static" */'./components/static/features'); -const GroupPlansPage = () => import(/* webpackChunkName: "static" */'./components/static/groupPlans'); -const MerchPage = () => import(/* webpackChunkName: "static" */'./components/static/merch'); -const NewsPage = () => import(/* webpackChunkName: "static" */'./components/static/newStuff'); -const OverviewPage = () => import(/* webpackChunkName: "static" */'./components/static/overview'); -const PressKitPage = () => import(/* webpackChunkName: "static" */'./components/static/pressKit'); -const PrivacyPage = () => import(/* webpackChunkName: "static" */'./components/static/privacy'); -const TermsPage = () => import(/* webpackChunkName: "static" */'./components/static/terms'); +const AppPage = () => import(/* webpackChunkName: "static" */'client/components/static/app'); +const ClearBrowserDataPage = () => import(/* webpackChunkName: "static" */'client/components/static/clearBrowserData'); +const CommunityGuidelinesPage = () => import(/* webpackChunkName: "static" */'client/components/static/communityGuidelines'); +const ContactPage = () => import(/* webpackChunkName: "static" */'client/components/static/contact'); +const FAQPage = () => import(/* webpackChunkName: "static" */'client/components/static/faq'); +const FeaturesPage = () => import(/* webpackChunkName: "static" */'client/components/static/features'); +const GroupPlansPage = () => import(/* webpackChunkName: "static" */'client/components/static/groupPlans'); +const MerchPage = () => import(/* webpackChunkName: "static" */'client/components/static/merch'); +const NewsPage = () => import(/* webpackChunkName: "static" */'client/components/static/newStuff'); +const OverviewPage = () => import(/* webpackChunkName: "static" */'client/components/static/overview'); +const PressKitPage = () => import(/* webpackChunkName: "static" */'client/components/static/pressKit'); +const PrivacyPage = () => import(/* webpackChunkName: "static" */'client/components/static/privacy'); +const TermsPage = () => import(/* webpackChunkName: "static" */'client/components/static/terms'); -const RegisterLoginReset = () => import(/* webpackChunkName: "auth" */'./components/auth/registerLoginReset'); -const Logout = () => import(/* webpackChunkName: "auth" */'./components/auth/logout'); +const RegisterLoginReset = () => import(/* webpackChunkName: "auth" */'client/components/auth/registerLoginReset'); +const Logout = () => import(/* webpackChunkName: "auth" */'client/components/auth/logout'); // User Pages // const StatsPage = () => import(/* webpackChunkName: "user" */'./components/userMenu/stats'); // const AchievementsPage = () => import(/* webpackChunkName: "user" */'./components/userMenu/achievements'); -const ProfilePage = () => import(/* webpackChunkName: "user" */'./components/userMenu/profilePage'); +const ProfilePage = () => import(/* webpackChunkName: "user" */'client/components/userMenu/profilePage'); // Settings -const Settings = () => import(/* webpackChunkName: "settings" */'./components/settings/index'); -const API = () => import(/* webpackChunkName: "settings" */'./components/settings/api'); -const DataExport = () => import(/* webpackChunkName: "settings" */'./components/settings/dataExport'); -const Notifications = () => import(/* webpackChunkName: "settings" */'./components/settings/notifications'); -const PromoCode = () => import(/* webpackChunkName: "settings" */'./components/settings/promoCode'); -const Site = () => import(/* webpackChunkName: "settings" */'./components/settings/site'); -const Subscription = () => import(/* webpackChunkName: "settings" */'./components/settings/subscription'); +const Settings = () => import(/* webpackChunkName: "settings" */'client/components/settings/index'); +const API = () => import(/* webpackChunkName: "settings" */'client/components/settings/api'); +const DataExport = () => import(/* webpackChunkName: "settings" */'client/components/settings/dataExport'); +const Notifications = () => import(/* webpackChunkName: "settings" */'client/components/settings/notifications'); +const PromoCode = () => import(/* webpackChunkName: "settings" */'client/components/settings/promoCode'); +const Site = () => import(/* webpackChunkName: "settings" */'client/components/settings/site'); +const Subscription = () => import(/* webpackChunkName: "settings" */'client/components/settings/subscription'); // Hall -const HallPage = () => import(/* webpackChunkName: "hall" */'./components/hall/index'); -const PatronsPage = () => import(/* webpackChunkName: "hall" */'./components/hall/patrons'); -const HeroesPage = () => import(/* webpackChunkName: "hall" */'./components/hall/heroes'); +const HallPage = () => import(/* webpackChunkName: "hall" */'client/components/hall/index'); +const PatronsPage = () => import(/* webpackChunkName: "hall" */'client/components/hall/patrons'); +const HeroesPage = () => import(/* webpackChunkName: "hall" */'client/components/hall/heroes'); // Except for tasks that are always loaded all the other main level // All the main level @@ -55,41 +53,41 @@ const HeroesPage = () => import(/* webpackChunkName: "hall" */'./components/hall // for docs // Tasks -const UserTasks = () => import(/* webpackChunkName: "userTasks" */'./components/tasks/user'); +const UserTasks = () => import(/* webpackChunkName: "userTasks" */'client/components/tasks/user'); // Inventory -const InventoryContainer = () => import(/* webpackChunkName: "inventory" */'./components/inventory/index'); -const ItemsPage = () => import(/* webpackChunkName: "inventory" */'./components/inventory/items/index'); -const EquipmentPage = () => import(/* webpackChunkName: "inventory" */'./components/inventory/equipment/index'); -const StablePage = () => import(/* webpackChunkName: "inventory" */'./components/inventory/stable/index'); +const InventoryContainer = () => import(/* webpackChunkName: "inventory" */'client/components/inventory/index'); +const ItemsPage = () => import(/* webpackChunkName: "inventory" */'client/components/inventory/items/index'); +const EquipmentPage = () => import(/* webpackChunkName: "inventory" */'client/components/inventory/equipment/index'); +const StablePage = () => import(/* webpackChunkName: "inventory" */'client/components/inventory/stable/index'); // Guilds -const GuildIndex = () => import(/* webpackChunkName: "guilds" */ './components/groups/index'); -const TavernPage = () => import(/* webpackChunkName: "guilds" */ './components/groups/tavern'); -const MyGuilds = () => import(/* webpackChunkName: "guilds" */ './components/groups/myGuilds'); -const GuildsDiscoveryPage = () => import(/* webpackChunkName: "guilds" */ './components/groups/discovery'); -const GroupPage = () => import(/* webpackChunkName: "guilds" */ './components/groups/group'); -const GroupPlansAppPage = () => import(/* webpackChunkName: "guilds" */ './components/groups/groupPlan'); +const GuildIndex = () => import(/* webpackChunkName: "guilds" */ 'client/components/groups/index'); +const TavernPage = () => import(/* webpackChunkName: "guilds" */ 'client/components/groups/tavern'); +const MyGuilds = () => import(/* webpackChunkName: "guilds" */ 'client/components/groups/myGuilds'); +const GuildsDiscoveryPage = () => import(/* webpackChunkName: "guilds" */ 'client/components/groups/discovery'); +const GroupPage = () => import(/* webpackChunkName: "guilds" */ 'client/components/groups/group'); +const GroupPlansAppPage = () => import(/* webpackChunkName: "guilds" */ 'client/components/groups/groupPlan'); // Group Plans -const GroupPlanIndex = () => import(/* webpackChunkName: "group-plans" */ './components/group-plans/index'); -const GroupPlanTaskInformation = () => import(/* webpackChunkName: "group-plans" */ './components/group-plans/taskInformation'); -const GroupPlanBilling = () => import(/* webpackChunkName: "group-plans" */ './components/group-plans/billing'); +const GroupPlanIndex = () => import(/* webpackChunkName: "group-plans" */ 'client/components/group-plans/index'); +const GroupPlanTaskInformation = () => import(/* webpackChunkName: "group-plans" */ 'client/components/group-plans/taskInformation'); +const GroupPlanBilling = () => import(/* webpackChunkName: "group-plans" */ 'client/components/group-plans/billing'); // Challenges -const ChallengeIndex = () => import(/* webpackChunkName: "challenges" */ './components/challenges/index'); -const MyChallenges = () => import(/* webpackChunkName: "challenges" */ './components/challenges/myChallenges'); -const FindChallenges = () => import(/* webpackChunkName: "challenges" */ './components/challenges/findChallenges'); -const ChallengeDetail = () => import(/* webpackChunkName: "challenges" */ './components/challenges/challengeDetail'); +const ChallengeIndex = () => import(/* webpackChunkName: "challenges" */ 'client/components/challenges/index'); +const MyChallenges = () => import(/* webpackChunkName: "challenges" */ 'client/components/challenges/myChallenges'); +const FindChallenges = () => import(/* webpackChunkName: "challenges" */ 'client/components/challenges/findChallenges'); +const ChallengeDetail = () => import(/* webpackChunkName: "challenges" */ 'client/components/challenges/challengeDetail'); // Shops -const ShopsContainer = () => import(/* webpackChunkName: "shops" */'./components/shops/index'); -const MarketPage = () => import(/* webpackChunkName: "shops-market" */'./components/shops/market/index'); -const QuestsPage = () => import(/* webpackChunkName: "shops-quest" */'./components/shops/quests/index'); -const SeasonalPage = () => import(/* webpackChunkName: "shops-seasonal" */'./components/shops/seasonal/index'); -const TimeTravelersPage = () => import(/* webpackChunkName: "shops-timetravelers" */'./components/shops/timeTravelers/index'); +const ShopsContainer = () => import(/* webpackChunkName: "shops" */'client/components/shops/index'); +const MarketPage = () => import(/* webpackChunkName: "shops-market" */'client/components/shops/market/index'); +const QuestsPage = () => import(/* webpackChunkName: "shops-quest" */'client/components/shops/quests/index'); +const SeasonalPage = () => import(/* webpackChunkName: "shops-seasonal" */'client/components/shops/seasonal/index'); +const TimeTravelersPage = () => import(/* webpackChunkName: "shops-timetravelers" */'client/components/shops/timeTravelers/index'); -import NotFoundPage from './components/404'; +import NotFoundPage from 'client/components/404'; Vue.use(VueRouter); @@ -279,6 +277,9 @@ const router = new VueRouter({ { name: 'contributors', path: 'contributors', component: HeroesPage }, ], }, + // Only used to handle some redirects + // See router.beforeEach + { path: '/redirect/:redirect', name: 'redirect' }, { path: '*', redirect: { name: 'notFound' } }, ], }); @@ -289,6 +290,8 @@ router.beforeEach(function routerGuard (to, from, next) { const isUserLoggedIn = store.state.isUserLoggedIn; const routeRequiresLogin = to.meta.requiresLogin !== false; + if (to.name === 'redirect') return handleRedirect(to, from, next); + if (!isUserLoggedIn && routeRequiresLogin) { // Redirect to the login page unless the user is trying to reach the // root of the website, in which case show the home page. diff --git a/website/server/controllers/top-level/payments/paypal.js b/website/server/controllers/top-level/payments/paypal.js index 6461bccc79..100c5d7361 100644 --- a/website/server/controllers/top-level/payments/paypal.js +++ b/website/server/controllers/top-level/payments/paypal.js @@ -61,7 +61,7 @@ api.checkoutSuccess = { if (req.query.noRedirect) { res.respond(200); } else { - res.redirect('/'); + res.redirect('/redirect/paypal-success-checkout'); } }, }; @@ -122,7 +122,7 @@ api.subscribeSuccess = { if (req.query.noRedirect) { res.respond(200); } else { - res.redirect('/user/settings/subscription'); + res.redirect('/redirect/paypal-success-subscribe'); } }, }; From 433c73c9d3346a2bdd96505843fd746af8c02de4 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Sat, 10 Nov 2018 16:03:25 +0100 Subject: [PATCH 2/5] paypal: restore url after purchase --- website/client/components/payments/buyGemsModal.vue | 4 ++-- website/client/components/settings/subscription.vue | 2 +- website/client/libs/userlocalManager.js | 9 +++++++++ website/client/mixins/payments.js | 10 +++++++++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/website/client/components/payments/buyGemsModal.vue b/website/client/components/payments/buyGemsModal.vue index 8c32e26d02..c6d98c1f23 100644 --- a/website/client/components/payments/buyGemsModal.vue +++ b/website/client/components/payments/buyGemsModal.vue @@ -75,7 +75,7 @@ .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(:href='paypalCheckoutLink', target='_blank') + a.card-body.paypal(@click="openPaypal(paypalCheckoutLink)") img(src='~assets/images/paypal.png') .card.text-center.payment-method(@click="amazonPaymentsInit({type: 'single'})") .card-body.amazon @@ -158,7 +158,7 @@ .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(:href='paypalSubscriptionLink', target='_blank') + a.card-body.paypal(@click="openPaypal(paypalSubscriptionLink)") img(src='~assets/images/paypal.png') .card.text-center.payment-method .card-body.amazon(@click="amazonPaymentsInit({type: 'subscription', subscription: subscriptionPlan})") diff --git a/website/client/components/settings/subscription.vue b/website/client/components/settings/subscription.vue index c71ef673d8..82a9f1c63d 100644 --- a/website/client/components/settings/subscription.vue +++ b/website/client/components/settings/subscription.vue @@ -77,7 +77,7 @@ .col-md-4 button.purchase.btn.btn-primary(@click='showStripe({subscription:subscription.key, coupon:subscription.coupon})', :disabled='!subscription.key') {{ $t('card') }} .col-md-4 - a.purchase(:href='paypalPurchaseLink', :disabled='!subscription.key', target='_blank') + a.purchase(@click="openPaypal(paypalPurchaseLink)", :disabled='!subscription.key') img(src='https://www.paypalobjects.com/webstatic/en_US/i/buttons/pp-acceptance-small.png', :alt="$t('paypal')") .col-md-4 a.btn.btn-secondary.purchase(@click="payWithAmazon()") diff --git a/website/client/libs/userlocalManager.js b/website/client/libs/userlocalManager.js index 1c2f00343a..da3e30489a 100644 --- a/website/client/libs/userlocalManager.js +++ b/website/client/libs/userlocalManager.js @@ -14,6 +14,9 @@ const CONSTANTS = { COSTUME_TAB: 'costume-tab', EQUIPMENT_TAB: 'equipment-tab', }, + savedAppStateValues: { + SAVED_APP_STATE: 'saved-app-state', + }, }; function setLocalSetting (key, value) { @@ -24,8 +27,14 @@ function getLocalSetting (key) { return localStorage.getItem(key); } +function removeLocalSetting (key) { + return localStorage.removeItem(key); +} + + export { CONSTANTS, getLocalSetting, setLocalSetting, + removeLocalSetting, }; diff --git a/website/client/mixins/payments.js b/website/client/mixins/payments.js index 71078350fc..250eeee67d 100644 --- a/website/client/mixins/payments.js +++ b/website/client/mixins/payments.js @@ -6,12 +6,12 @@ import { mapState } from 'client/libs/store'; import encodeParams from 'client/libs/encodeParams'; import notificationsMixin from 'client/mixins/notifications'; import * as Analytics from 'client/libs/analytics'; +import { CONSTANTS, setLocalSetting } from 'client/libs/userlocalManager'; export default { mixins: [notificationsMixin], computed: { ...mapState(['credentials']), - // @TODO refactor into one single computed property paypalCheckoutLink () { return '/paypal/checkout'; }, @@ -41,6 +41,14 @@ export default { let gift = this.encodeGift(data.giftedTo, data.gift); const url = `/paypal/checkout?gift=${gift}`; + this.openPaypal(url); + }, + openPaypal (url) { + const appState = { + path: this.$route.fullPath, + paymentMethod: 'paypal', + }; + setLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE, JSON.stringify(appState)); window.open(url, '_blank'); }, showStripe (data) { From cc751960aca41bc41ea71452e351f610e24687bf Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Wed, 21 Nov 2018 11:42:32 +0100 Subject: [PATCH 3/5] save state and close tab afetr success --- website/client/mixins/payments.js | 2 +- website/client/router/handleRedirect.js | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/website/client/mixins/payments.js b/website/client/mixins/payments.js index 250eeee67d..d2202bb86e 100644 --- a/website/client/mixins/payments.js +++ b/website/client/mixins/payments.js @@ -45,7 +45,7 @@ export default { }, openPaypal (url) { const appState = { - path: this.$route.fullPath, + inProgress: 'payment', paymentMethod: 'paypal', }; setLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE, JSON.stringify(appState)); diff --git a/website/client/router/handleRedirect.js b/website/client/router/handleRedirect.js index dd5dee4fe6..20c3be121c 100644 --- a/website/client/router/handleRedirect.js +++ b/website/client/router/handleRedirect.js @@ -1,12 +1,13 @@ +import { CONSTANTS, removeLocalSetting } from 'client/libs/userlocalManager'; + export default function (to, from, next) { const redirect = to.params.redirect; switch (redirect) { case 'paypal-success-checkout': - console.log('checkout'); - break; case 'paypal-success-subscribe': - console.log('subscribe'); + removeLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE); + window.close(); break; default: next({name: 'notFound'}); From e212842b504da6d74227959ab043d606589fd721 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Sun, 2 Dec 2018 15:44:47 +0100 Subject: [PATCH 4/5] fix redirects and basic success message --- website/client/app.vue | 10 ++++++ .../components/payments/amazonModal.vue | 17 ++++++++-- .../components/payments/buyGemsModal.vue | 4 +-- .../components/settings/subscription.vue | 2 +- website/client/mixins/payments.js | 32 ++++++++++++++----- website/client/router/handleRedirect.js | 12 +++++-- 6 files changed, 60 insertions(+), 17 deletions(-) diff --git a/website/client/app.vue b/website/client/app.vue index 6583241956..9de0d68c57 100644 --- a/website/client/app.vue +++ b/website/client/app.vue @@ -186,6 +186,7 @@ import notifications from 'client/mixins/notifications'; import { setup as setupPayments } from 'client/libs/payments'; import amazonPaymentsModal from 'client/components/payments/amazonModal'; import spellsMixin from 'client/mixins/spells'; +import { CONSTANTS, getLocalSetting, removeLocalSetting } from 'client/libs/userlocalManager'; import svgClose from 'assets/svg/close.svg'; import bannedAccountModal from 'client/components/bannedAccountModal'; @@ -433,6 +434,15 @@ export default { }); } + let appState = getLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE); + if (appState) { + appState = JSON.parse(appState); + if (appState.paymentCompleted) { + alert(`payment completed with ${appState.paymentMethod}`); + removeLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE); + } + } + this.$nextTick(() => { // Load external scripts after the app has been rendered setupPayments(); diff --git a/website/client/components/payments/amazonModal.vue b/website/client/components/payments/amazonModal.vue index 347c768fcd..18864345ef 100644 --- a/website/client/components/payments/amazonModal.vue +++ b/website/client/components/payments/amazonModal.vue @@ -33,6 +33,7 @@ import * as Analytics from 'client/libs/analytics'; import axios from 'axios'; import { mapState } from 'client/libs/store'; +import { CONSTANTS, setLocalSetting } from 'client/libs/userlocalManager'; const AMAZON_PAYMENTS = process.env.AMAZON_PAYMENTS; // eslint-disable-line @@ -186,6 +187,14 @@ export default { new this.OffAmazonPayments.Widgets.Wallet(walletParams).bind('AmazonPayWallet'); }, + storePaymentStatusAndReload () { + const appState = { + paymentMethod: 'amazon', + paymentCompleted: true, + }; + setLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE, JSON.stringify(appState)); + window.location.reload(true); + }, async amazonCheckOut () { this.amazonButtonEnabled = false; @@ -202,8 +211,7 @@ export default { this.$set(this, 'amazonButtonEnabled', true); this.reset(); - // @TODO: What are we syncing? - window.location.reload(true); + this.storePaymentStatusAndReload(); } catch (e) { this.$set(this, 'amazonButtonEnabled', true); this.amazonPaymentsreset(); @@ -241,22 +249,25 @@ export default { }); location.href = `${habiticaUrl}/group-plans/${newGroup._id}/task-information?showGroupOverview=true`; + this.storePaymentStatusAndReload(); return; } // @TODO: Just append? or $emit? this.$router.push(`/group-plans/${newGroup._id}/task-information`); this.user.guilds.push(newGroup._id); + this.storePaymentStatusAndReload(); return; } if (this.amazonPayments.groupId) { this.$router.push(`/group-plans/${this.amazonPayments.groupId}/task-information`); + this.storePaymentStatusAndReload(); return; } - window.location.reload(true); this.reset(); + this.storePaymentStatusAndReload(); } catch (e) { this.$set(this, 'amazonButtonEnabled', true); // @TODO: do we need this? this.amazonPaymentsreset(); diff --git a/website/client/components/payments/buyGemsModal.vue b/website/client/components/payments/buyGemsModal.vue index c6d98c1f23..f0dd48b2b9 100644 --- a/website/client/components/payments/buyGemsModal.vue +++ b/website/client/components/payments/buyGemsModal.vue @@ -75,7 +75,7 @@ .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)") + 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 @@ -158,7 +158,7 @@ .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)") + 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})") diff --git a/website/client/components/settings/subscription.vue b/website/client/components/settings/subscription.vue index 82a9f1c63d..da71ec2c86 100644 --- a/website/client/components/settings/subscription.vue +++ b/website/client/components/settings/subscription.vue @@ -77,7 +77,7 @@ .col-md-4 button.purchase.btn.btn-primary(@click='showStripe({subscription:subscription.key, coupon:subscription.coupon})', :disabled='!subscription.key') {{ $t('card') }} .col-md-4 - a.purchase(@click="openPaypal(paypalPurchaseLink)", :disabled='!subscription.key') + a.purchase(@click="openPaypal(paypalPurchaseLink, 'subscription')", :disabled='!subscription.key') img(src='https://www.paypalobjects.com/webstatic/en_US/i/buttons/pp-acceptance-small.png', :alt="$t('paypal')") .col-md-4 a.btn.btn-secondary.purchase(@click="payWithAmazon()") diff --git a/website/client/mixins/payments.js b/website/client/mixins/payments.js index d2202bb86e..8689a8f74e 100644 --- a/website/client/mixins/payments.js +++ b/website/client/mixins/payments.js @@ -41,15 +41,26 @@ export default { let gift = this.encodeGift(data.giftedTo, data.gift); const url = `/paypal/checkout?gift=${gift}`; - this.openPaypal(url); + this.openPaypal(url, 'gift'); }, - openPaypal (url) { + openPaypal (url/* , type*/) { const appState = { - inProgress: 'payment', paymentMethod: 'paypal', + paymentCompleted: false, }; setLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE, JSON.stringify(appState)); window.open(url, '_blank'); + + function localStorageChangeHandled (e) { + if (e.key === 'saved-app-state') { + window.removeEventListener('storage', localStorageChangeHandled); + const newState = e.newValue ? JSON.parse(e.newValue) : {}; + if (newState.paymentCompleted) window.location.reload(true); + } + } + + // Listen for changes to local storage, indicating that the payment completed + window.addEventListener('storage', localStorageChangeHandled); }, showStripe (data) { if (!this.checkGemAmount(data)) return; @@ -100,6 +111,12 @@ export default { return; } + const appState = { + paymentMethod: 'stripe', + paymentCompleted: true, + }; + setLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE, JSON.stringify(appState)); + let newGroup = response.data.data; if (newGroup && newGroup._id) { // @TODO this does not do anything as we reload just below @@ -117,18 +134,17 @@ export default { }); location.href = `${habiticaUrl}/group-plans/${newGroup._id}/task-information?showGroupOverview=true`; - return; + window.location.reload(true); } this.$router.push(`/group-plans/${newGroup._id}/task-information`); - // @TODO action this.user.guilds.push(newGroup._id); - return; + window.location.reload(true); } if (data.groupId) { this.$router.push(`/group-plans/${data.groupId}/task-information`); - return; + window.location.reload(true); } window.location.reload(true); @@ -152,7 +168,7 @@ export default { let url = '/stripe/subscribe/edit'; let response = await axios.post(url, data); - // Succss + // Success window.location.reload(true); // error alert(response.message); diff --git a/website/client/router/handleRedirect.js b/website/client/router/handleRedirect.js index 20c3be121c..0aba06afec 100644 --- a/website/client/router/handleRedirect.js +++ b/website/client/router/handleRedirect.js @@ -1,14 +1,20 @@ -import { CONSTANTS, removeLocalSetting } from 'client/libs/userlocalManager'; +import { CONSTANTS, getLocalSetting, setLocalSetting } from 'client/libs/userlocalManager'; export default function (to, from, next) { const redirect = to.params.redirect; switch (redirect) { case 'paypal-success-checkout': - case 'paypal-success-subscribe': - removeLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE); + case 'paypal-success-subscribe': { + const appState = getLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE); + if (appState) { + const newAppState = JSON.parse(appState); + newAppState.paymentCompleted = true; + setLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE, JSON.stringify(newAppState)); + } window.close(); break; + } default: next({name: 'notFound'}); } From b9e12aca3ef8d57c8d629a1defc87a3e79671c1c Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Sun, 2 Dec 2018 17:33:21 +0100 Subject: [PATCH 5/5] success modal and fix redirects --- website/client/app.vue | 7 +- .../components/payments/amazonModal.vue | 21 +++-- .../components/payments/successModal.vue | 86 +++++++++++++++++++ website/client/mixins/payments.js | 16 ++-- website/common/locales/en/npc.json | 1 + 5 files changed, 110 insertions(+), 21 deletions(-) create mode 100644 website/client/components/payments/successModal.vue diff --git a/website/client/app.vue b/website/client/app.vue index 9de0d68c57..9e7bccbb49 100644 --- a/website/client/app.vue +++ b/website/client/app.vue @@ -11,6 +11,7 @@ div #app(:class='{"casting-spell": castingSpell}') banned-account-modal amazon-payments-modal(v-if='!isStaticPage') + payments-success-modal snackbars router-view(v-if="!isUserLoggedIn || isStaticPage") template(v-else) @@ -185,6 +186,8 @@ import SelectMembersModal from 'client/components/selectMembersModal.vue'; import notifications from 'client/mixins/notifications'; import { setup as setupPayments } from 'client/libs/payments'; import amazonPaymentsModal from 'client/components/payments/amazonModal'; +import paymentsSuccessModal from 'client/components/payments/successModal'; + import spellsMixin from 'client/mixins/spells'; import { CONSTANTS, getLocalSetting, removeLocalSetting } from 'client/libs/userlocalManager'; @@ -206,6 +209,7 @@ export default { SelectMembersModal, amazonPaymentsModal, bannedAccountModal, + paymentsSuccessModal, }, data () { return { @@ -438,11 +442,10 @@ export default { if (appState) { appState = JSON.parse(appState); if (appState.paymentCompleted) { - alert(`payment completed with ${appState.paymentMethod}`); removeLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE); + this.$root.$emit('bv::show::modal', 'payments-success-modal'); } } - this.$nextTick(() => { // Load external scripts after the app has been rendered setupPayments(); diff --git a/website/client/components/payments/amazonModal.vue b/website/client/components/payments/amazonModal.vue index 18864345ef..513907788a 100644 --- a/website/client/components/payments/amazonModal.vue +++ b/website/client/components/payments/amazonModal.vue @@ -36,6 +36,7 @@ import { mapState } from 'client/libs/store'; import { CONSTANTS, setLocalSetting } from 'client/libs/userlocalManager'; const AMAZON_PAYMENTS = process.env.AMAZON_PAYMENTS; // eslint-disable-line +const habiticaUrl = `${location.protocol}//${location.host}`; export default { data () { @@ -187,13 +188,17 @@ export default { new this.OffAmazonPayments.Widgets.Wallet(walletParams).bind('AmazonPayWallet'); }, - storePaymentStatusAndReload () { + storePaymentStatusAndReload (url) { const appState = { paymentMethod: 'amazon', paymentCompleted: true, }; setLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE, JSON.stringify(appState)); - window.location.reload(true); + if (url) { + window.location.assign(url); + } else { + window.location.reload(true); + } }, async amazonCheckOut () { this.amazonButtonEnabled = false; @@ -239,8 +244,6 @@ export default { if (newGroup && newGroup._id) { // Handle new user signup if (!this.$store.state.isUserLoggedIn) { - const habiticaUrl = `${location.protocol}//${location.host}`; - Analytics.track({ hitType: 'event', eventCategory: 'group-plans-static', @@ -248,21 +251,17 @@ export default { eventLabel: 'paid-with-amazon', }); - location.href = `${habiticaUrl}/group-plans/${newGroup._id}/task-information?showGroupOverview=true`; - this.storePaymentStatusAndReload(); + this.storePaymentStatusAndReload(`${habiticaUrl}/group-plans/${newGroup._id}/task-information?showGroupOverview=true`); return; } - // @TODO: Just append? or $emit? - this.$router.push(`/group-plans/${newGroup._id}/task-information`); this.user.guilds.push(newGroup._id); - this.storePaymentStatusAndReload(); + this.storePaymentStatusAndReload(`${habiticaUrl}/group-plans/${newGroup._id}/task-information`); return; } if (this.amazonPayments.groupId) { - this.$router.push(`/group-plans/${this.amazonPayments.groupId}/task-information`); - this.storePaymentStatusAndReload(); + this.storePaymentStatusAndReload(`${habiticaUrl}/group-plans/${this.amazonPayments.groupId}/task-information`); return; } diff --git a/website/client/components/payments/successModal.vue b/website/client/components/payments/successModal.vue new file mode 100644 index 0000000000..08fda4589b --- /dev/null +++ b/website/client/components/payments/successModal.vue @@ -0,0 +1,86 @@ + + + + + \ No newline at end of file diff --git a/website/client/mixins/payments.js b/website/client/mixins/payments.js index 8689a8f74e..dee1aa4db4 100644 --- a/website/client/mixins/payments.js +++ b/website/client/mixins/payments.js @@ -8,6 +8,8 @@ import notificationsMixin from 'client/mixins/notifications'; import * as Analytics from 'client/libs/analytics'; import { CONSTANTS, setLocalSetting } from 'client/libs/userlocalManager'; +const habiticaUrl = `${location.protocol}//${location.host}`; + export default { mixins: [notificationsMixin], computed: { @@ -124,8 +126,6 @@ export default { // Handle new user signup if (!this.$store.state.isUserLoggedIn) { - const habiticaUrl = `${location.protocol}//${location.host}`; - Analytics.track({ hitType: 'event', eventCategory: 'group-plans-static', @@ -133,18 +133,18 @@ export default { eventLabel: 'paid-with-stripe', }); - location.href = `${habiticaUrl}/group-plans/${newGroup._id}/task-information?showGroupOverview=true`; - window.location.reload(true); + window.location.assign(`${habiticaUrl}/group-plans/${newGroup._id}/task-information?showGroupOverview=true`); + return; } - this.$router.push(`/group-plans/${newGroup._id}/task-information`); this.user.guilds.push(newGroup._id); - window.location.reload(true); + window.location.assign(`${habiticaUrl}/group-plans/${newGroup._id}/task-information`); + return; } if (data.groupId) { - this.$router.push(`/group-plans/${data.groupId}/task-information`); - window.location.reload(true); + window.location.assign(`${habiticaUrl}/group-plans/${data.groupId}/task-information`); + return; } window.location.reload(true); diff --git a/website/common/locales/en/npc.json b/website/common/locales/en/npc.json index f9e862ccd3..da725f429f 100644 --- a/website/common/locales/en/npc.json +++ b/website/common/locales/en/npc.json @@ -115,6 +115,7 @@ "card": "Credit Card (using Stripe)", "amazonInstructions": "Click the button to pay using Amazon Payments", "paymentMethods": "Purchase using", + "paymentSuccessful": "Your payment was successful!", "classGear": "Class Gear", "classGearText": "Congratulations on choosing a class! I've added your new basic weapon to your inventory. Take a look below to equip it!",