diff --git a/ghost/admin/app/components/gh-billing-iframe.js b/ghost/admin/app/components/gh-billing-iframe.js index 3a1344070c..b02a6b772f 100644 --- a/ghost/admin/app/components/gh-billing-iframe.js +++ b/ghost/admin/app/components/gh-billing-iframe.js @@ -9,6 +9,8 @@ export default Component.extend({ ajax: service(), notifications: service(), + isOwner: null, + didInsertElement() { this._super(...arguments); @@ -27,13 +29,21 @@ export default Component.extend({ request: 'token', response: token }, '*'); + + this.set('isOwner', true); }).catch((error) => { if (error.payload?.errors && error.payload.errors[0]?.type === 'NoPermissionError') { - // noop - user doesn't have permission to access billing - return; - } + // no permission means the current user requesting the token is not the owner of the site. + this.set('isOwner', false); - throw error; + // Avoid letting the BMA waiting for a message and send an empty token response instead + this.billing.getBillingIframe().contentWindow.postMessage({ + request: 'token', + response: null + }, '*'); + } else { + throw error; + } }); // NOTE: the handler is placed here to avoid additional logic to check if iframe has loaded @@ -47,6 +57,27 @@ export default Component.extend({ } } + if (event && event.data && event.data.request === 'forceUpgradeInfo') { + // Send BMA requested information about forceUpgrade and owner name/email + let ownerUser = null; + const owner = this.billing.ownerUser; + + if (owner) { + ownerUser = { + name: owner?.name, + email: owner?.email + }; + } + this.billing.getBillingIframe().contentWindow.postMessage({ + request: 'forceUpgradeInfo', + response: { + forceUpgrade: this.config.get('hostSettings.forceUpgrade'), + isOwner: this.isOwner, + ownerUser + } + }, '*'); + } + if (event && event.data && event.data.subscription) { this.billing.set('subscription', event.data.subscription); this.billing.set('checkoutRoute', event.data?.checkoutRoute || '/plans'); diff --git a/ghost/admin/app/routes/application.js b/ghost/admin/app/routes/application.js index 661c631df0..f357219e5e 100644 --- a/ghost/admin/app/routes/application.js +++ b/ghost/admin/app/routes/application.js @@ -36,6 +36,7 @@ export default Route.extend(ShortcutsRoute, { settings: service(), ui: service(), whatsNew: service(), + billing: service(), shortcuts, @@ -177,6 +178,11 @@ export default Route.extend(ShortcutsRoute, { await this.session.postAuthPreparation(); } + + if (this.config.get('hostSettings.forceUpgrade')) { + // enforce opening the BMA in a force upgrade state + this.billing.openBillingWindow(this.router.currentURL, '/pro'); + } } }); diff --git a/ghost/admin/app/routes/pro.js b/ghost/admin/app/routes/pro.js index a5cc183b32..aebb86493f 100644 --- a/ghost/admin/app/routes/pro.js +++ b/ghost/admin/app/routes/pro.js @@ -5,6 +5,7 @@ import {inject as service} from '@ember/service'; export default AuthenticatedRoute.extend({ billing: service(), session: service(), + config: service(), queryParams: { action: {refreshModel: true} @@ -13,7 +14,8 @@ export default AuthenticatedRoute.extend({ beforeModel(transition) { this._super(...arguments); - if (!this.session.user.isOwnerOnly) { + // allow non-owner users to access the BMA when we're in a force upgrade state + if (!this.session.user.isOwnerOnly && !this.config.get('hostSettings.forceUpgrade')) { return this.transitionTo('home'); } diff --git a/ghost/admin/app/services/billing.js b/ghost/admin/app/services/billing.js index 155da78fb8..52c6ba07fb 100644 --- a/ghost/admin/app/services/billing.js +++ b/ghost/admin/app/services/billing.js @@ -5,12 +5,14 @@ export default Service.extend({ router: service(), config: service(), ghostPaths: service(), + store: service(), billingRouteRoot: '#/pro', billingWindowOpen: false, subscription: null, previousRoute: null, action: null, + ownerUser: null, init() { this._super(...arguments); @@ -52,6 +54,21 @@ export default Service.extend({ return url; }, + async getOwnerUser() { + if (!this.get('ownerUser')) { + // Try to receive the owner user from the store + let user = this.store.peekAll('user').findBy('isOwnerOnly', true); + + if (!user) { + // load it when it's not there yet + await this.store.findAll('user'); + user = this.store.peekAll('user').findBy('isOwnerOnly', true); + } + this.set('ownerUser', user); + } + return this.get('ownerUser'); + }, + // Sends a route update to a child route in the BMA, because we can't control // navigating to it otherwise sendRouteUpdate() { @@ -88,6 +105,9 @@ export default Service.extend({ // remembering the route from which the action has been triggered - "previousRoute" so it // could be reused when closing billing window openBillingWindow(currentRoute, childRoute) { + // initiate getting owner user in the background + this.getOwnerUser(); + if (this.get('billingWindowOpen')) { // don't attempt to open again return; diff --git a/ghost/admin/app/utils/route.js b/ghost/admin/app/utils/route.js index 939e48c057..c2eaae77d5 100644 --- a/ghost/admin/app/utils/route.js +++ b/ghost/admin/app/utils/route.js @@ -1,12 +1,22 @@ import Route from '@ember/routing/route'; +import {inject as service} from '@ember/service'; Route.reopen({ + config: service(), + billing: service(), + router: service(), + actions: { willTransition(transition) { if (this.get('upgradeStatus.isRequired')) { transition.abort(); this.upgradeStatus.requireUpgrade(); return false; + } else if (this.config.get('hostSettings.forceUpgrade')) { + transition.abort(); + // Catch and redirect every route in a force upgrade state + this.billing.openBillingWindow(this.router.currentURL, '/pro'); + return false; } else { return true; }