mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-24 23:48:13 -05:00
Added SSL precondition for Stripe Connect UI (#1967)
refs https://github.com/TryGhost/Team/issues/598 Stripe Webhooks require SSL in production, and so we should not be allowing connecting to Stripe in production mode unless the site is running with SSL. This change - - Updates Setup wizard to skip Stripe Connect steps if site is not on SSL in production - Adds warning on set subscriptions page Co-authored-by: Peter Zimon <zimo@ghost.org>
This commit is contained in:
parent
5422496dbc
commit
69367de59e
10 changed files with 192 additions and 45 deletions
|
@ -1,7 +1,39 @@
|
|||
<div class="gh-launch-wizard-settings-container">
|
||||
{{#if this.isHidden}}
|
||||
<h4 class="gh-setting-title">Subscriptions already set</h4>
|
||||
<p>You can change product subscription pricing and Portal look and feel in Settings.</p>
|
||||
{{#if this.isConnectDisallowed}}
|
||||
<div class="gh-stack overflow-y-auto flex-grow-1">
|
||||
<div class="gh-setting-nossl-container">
|
||||
<span class="red">{{svg-jar "shield-lock"}}</span>
|
||||
<h4>Your site is not secured</h4>
|
||||
<p>Paid memberships through Ghost can only be run on sites secured by SSL (HTTPS vs. HTTP). More information on adding a free SSL Certificate to your Ghost site can be <a href="https://ghost.org/integrations/lets-encrypt/" target="_blank" rel="noopener noreferrer">found here</a>.</p>
|
||||
</div>
|
||||
<div class="w-100 mt6">
|
||||
<div class="gh-setting-title">Generate secure key</div>
|
||||
<div class="flex items-center mb4 gh-members-connectbutton-container justify-between mt2">
|
||||
<div class="stripe-connect disabled"><span>Connect with Stripe</span></div>
|
||||
<div class="ml2 flex items-center flex-nowrap">
|
||||
<span class="mr2 f8 midgrey nowrap">Test mode</span>
|
||||
<div class="for-switch small disabled">
|
||||
<label class="switch" for="stripe-connect-test-mode">
|
||||
<input type="checkbox" class="gh-input" disabled="disabled">
|
||||
<span class="input-toggle-component mt1"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-setting-action">
|
||||
<GhTextarea
|
||||
class="gh-launch-wizard-stripe-connect-token"
|
||||
placeholder="Paste your secure key here"
|
||||
disabled="disabled"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else if this.isHidden}}
|
||||
<div class="gh-stack overflow-y-auto flex-grow-1">
|
||||
<h4 class="gh-setting-title">Subscriptions already set</h4>
|
||||
<p>You can change product subscription pricing and signup options in Membership settings.</p>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="gh-stack overflow-y-auto flex-grow-1">
|
||||
<div class="gh-stack-item flex-column">
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Component from '@glimmer/component';
|
||||
import envConfig from 'ghost-admin/config/environment';
|
||||
import {action} from '@ember/object';
|
||||
import {currencies, getSymbol} from 'ghost-admin/utils/currency';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
@ -35,6 +36,12 @@ export default class GhLaunchWizardSetPricingComponent extends Component {
|
|||
return this.currencies.findBy('value', this.currency);
|
||||
}
|
||||
|
||||
get isConnectDisallowed() {
|
||||
const siteUrl = this.config.get('blogUrl');
|
||||
|
||||
return envConfig.environment !== 'development' && !/^https:/.test(siteUrl);
|
||||
}
|
||||
|
||||
get disabled() {
|
||||
if (this.product) {
|
||||
return this.product.get('stripePrices') && this.product.get('stripePrices').length > 0;
|
||||
|
@ -167,23 +174,27 @@ export default class GhLaunchWizardSetPricingComponent extends Component {
|
|||
|
||||
@task
|
||||
*saveAndContinue() {
|
||||
yield this.validateStripePlans();
|
||||
if (this.isHidden || !this.isConnectDisallowed) {
|
||||
this.args.nextStep();
|
||||
} else {
|
||||
yield this.validateStripePlans();
|
||||
|
||||
if (this.stripePlanError) {
|
||||
return false;
|
||||
if (this.stripePlanError) {
|
||||
return false;
|
||||
}
|
||||
const product = this.getProduct();
|
||||
const data = this.args.getData() || {};
|
||||
this.args.storeData({
|
||||
...data,
|
||||
product,
|
||||
isFreeChecked: this.isFreeChecked,
|
||||
isMonthlyChecked: this.isMonthlyChecked,
|
||||
isYearlyChecked: this.isYearlyChecked,
|
||||
monthlyAmount: this.stripeMonthlyAmount,
|
||||
yearlyAmount: this.stripeYearlyAmount
|
||||
});
|
||||
this.args.nextStep();
|
||||
}
|
||||
const product = this.getProduct();
|
||||
const data = this.args.getData() || {};
|
||||
this.args.storeData({
|
||||
...data,
|
||||
product,
|
||||
isFreeChecked: this.isFreeChecked,
|
||||
isMonthlyChecked: this.isMonthlyChecked,
|
||||
isYearlyChecked: this.isYearlyChecked,
|
||||
monthlyAmount: this.stripeMonthlyAmount,
|
||||
yearlyAmount: this.stripeYearlyAmount
|
||||
});
|
||||
this.args.nextStep();
|
||||
}
|
||||
|
||||
calculateDiscount(monthly, yearly) {
|
||||
|
|
|
@ -1,8 +1,34 @@
|
|||
import Controller from '@ember/controller';
|
||||
import envConfig from 'ghost-admin/config/environment';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
const DEFAULT_STEPS = {
|
||||
'customise-design': {
|
||||
title: 'Customise your site',
|
||||
position: 'Step 1',
|
||||
next: 'connect-stripe'
|
||||
},
|
||||
'connect-stripe': {
|
||||
title: 'Connect to Stripe',
|
||||
position: 'Step 2',
|
||||
next: 'set-pricing',
|
||||
back: 'customise-design',
|
||||
skip: 'finalise'
|
||||
},
|
||||
'set-pricing': {
|
||||
title: 'Set up subscriptions',
|
||||
position: 'Step 3',
|
||||
next: 'finalise',
|
||||
back: 'connect-stripe'
|
||||
},
|
||||
finalise: {
|
||||
title: 'Launch your site',
|
||||
position: 'Final step',
|
||||
back: 'set-pricing'
|
||||
}
|
||||
};
|
||||
export default class LaunchController extends Controller {
|
||||
@service config;
|
||||
@service router;
|
||||
|
@ -15,34 +41,38 @@ export default class LaunchController extends Controller {
|
|||
@tracked step = 'customise-design';
|
||||
@tracked data = null;
|
||||
|
||||
steps = {
|
||||
'customise-design': {
|
||||
title: 'Customise your site',
|
||||
position: 'Step 1',
|
||||
next: 'connect-stripe'
|
||||
},
|
||||
'connect-stripe': {
|
||||
title: 'Connect to Stripe',
|
||||
position: 'Step 2',
|
||||
next: 'set-pricing',
|
||||
back: 'customise-design',
|
||||
skip: 'finalise'
|
||||
},
|
||||
'set-pricing': {
|
||||
title: 'Set up subscriptions',
|
||||
position: 'Step 3',
|
||||
next: 'finalise',
|
||||
back: 'connect-stripe'
|
||||
},
|
||||
finalise: {
|
||||
title: 'Launch your site',
|
||||
position: 'Final step',
|
||||
back: 'set-pricing'
|
||||
}
|
||||
};
|
||||
steps = DEFAULT_STEPS;
|
||||
|
||||
skippedSteps = [];
|
||||
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
const siteUrl = this.config.get('blogUrl');
|
||||
|
||||
if (envConfig.environment !== 'development' && !/^https:/.test(siteUrl)) {
|
||||
this.steps = {
|
||||
'customise-design': {
|
||||
title: 'Customise your site',
|
||||
position: 'Step 1',
|
||||
next: 'set-pricing'
|
||||
},
|
||||
'set-pricing': {
|
||||
title: 'Set up subscriptions',
|
||||
position: 'Step 2',
|
||||
next: 'finalise',
|
||||
back: 'customise-design'
|
||||
},
|
||||
finalise: {
|
||||
title: 'Launch your site',
|
||||
position: 'Final step',
|
||||
back: 'set-pricing'
|
||||
}
|
||||
};
|
||||
} else {
|
||||
this.steps = DEFAULT_STEPS;
|
||||
}
|
||||
}
|
||||
|
||||
get currentStep() {
|
||||
return this.steps[this.step];
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Controller from '@ember/controller';
|
||||
import envConfig from 'ghost-admin/config/environment';
|
||||
import {action} from '@ember/object';
|
||||
import {currencies, getCurrencyOptions, getSymbol} from 'ghost-admin/utils/currency';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
@ -50,6 +51,11 @@ export default class MembersAccessController extends Controller {
|
|||
return CURRENCIES.findBy('value', this.currency);
|
||||
}
|
||||
|
||||
get isConnectDisallowed() {
|
||||
const siteUrl = this.config.get('blogUrl');
|
||||
return envConfig.environment !== 'development' && !/^https:/.test(siteUrl);
|
||||
}
|
||||
|
||||
leaveRoute(transition) {
|
||||
if (this.settings.get('hasDirtyAttributes')) {
|
||||
transition.abort();
|
||||
|
|
|
@ -85,6 +85,16 @@
|
|||
line-height: 0;
|
||||
}
|
||||
|
||||
.gh-launch-wizard-settings-container .gh-setting-nossl-container {
|
||||
border: 1px solid var(--whitegrey);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.gh-launch-wizard-settings-container .gh-setting-nossl-container svg {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
|
||||
/* Connect Stripe settings */
|
||||
.gh-launch-wizard-stripe-info {
|
||||
|
|
|
@ -1812,3 +1812,42 @@ p.theme-validation-details {
|
|||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.gh-setting-nossl {
|
||||
border-top: 1px solid var(--whitegrey-d1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 16px -24px -12px;
|
||||
}
|
||||
|
||||
.gh-setting-nossl-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 32px;
|
||||
text-align: center;
|
||||
max-width: 520px;
|
||||
}
|
||||
|
||||
.gh-setting-nossl-container svg {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.gh-setting-nossl-container svg path,
|
||||
.gh-setting-nossl-container svg rect,
|
||||
.gh-setting-nossl-container svg circle {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.gh-setting-nossl-container h4 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.gh-setting-nossl-container p {
|
||||
margin: 8px 0 0;
|
||||
color: var(--midgrey);
|
||||
}
|
||||
|
|
|
@ -742,4 +742,9 @@ Usage: CTA buttons grouped together horizontally.
|
|||
|
||||
.gh-btn-stripe-status.connected:before {
|
||||
background: var(--green);
|
||||
}
|
||||
|
||||
.stripe-connect.disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
|
@ -557,6 +557,10 @@ textarea {
|
|||
transform: translateX(16px);
|
||||
}
|
||||
|
||||
.for-switch.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Select
|
||||
/* ---------------------------------------------------------- */
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
<div class="gh-setting-members-tierscontainer">
|
||||
<div class="gh-settings-members-tiersheader">
|
||||
<h4 class="gh-main-section-header small bn">Membership tiers</h4>
|
||||
<button type="button" class="gh-btn gh-btn-outline gh-btn-stripe-status {{if this.settings.stripeConnectAccountId "connected" ""}}" {{action (toggle "showStripeConnect" this)}}>
|
||||
<button type="button" class="gh-btn gh-btn-outline gh-btn-stripe-status {{if this.isConnectDisallowed "disabled"}} {{if this.settings.stripeConnectAccountId "connected" ""}}" {{action (toggle "showStripeConnect" this)}}>
|
||||
<span>
|
||||
{{if this.settings.stripeConnectAccountId "Connected to Stripe" "Stripe not connected"}}
|
||||
</span>
|
||||
|
@ -114,11 +114,20 @@
|
|||
{{#if this.settings.stripeConnectAccountId}}
|
||||
<button type="button" class="gh-btn" {{action (toggle "paidOpen" this)}} data-test-toggle-pub-info><span>{{if this.paidOpen "Close" "Expand"}}</span></button>
|
||||
{{else}}
|
||||
<button type="button" class="stripe-connect" {{action (toggle "showStripeConnect" this)}}>
|
||||
<button type="button" class="stripe-connect {{if this.isConnectDisallowed "disabled"}}" {{action (toggle "showStripeConnect" this)}}>
|
||||
<span>Connect with Stripe</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if this.isConnectDisallowed}}
|
||||
<div class="gh-setting-nossl">
|
||||
<div class="gh-setting-nossl-container">
|
||||
<span class="red">{{svg-jar "shield-lock"}}</span>
|
||||
<h4>Your site is not secured</h4>
|
||||
<p>Paid memberships through Ghost can only be run on sites secured by SSL (HTTPS vs. HTTP). More information on adding a free SSL Certificate to your Ghost site can be <a href="https://ghost.org/integrations/lets-encrypt/" target="_blank" rel="noopener noreferrer">found here</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="gh-expandable-content">
|
||||
{{#liquid-if this.paidOpen}}
|
||||
<div class="gh-setting-content-extended">
|
||||
|
|
1
ghost/admin/public/assets/icons/shield-lock.svg
Normal file
1
ghost/admin/public/assets/icons/shield-lock.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs><style>.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}</style></defs><title>shield-lock</title><path class="a" d="M2.25,3.923v7.614A11.907,11.907,0,0,0,9.882,22.65l1.041.4a3,3,0,0,0,2.154,0l1.041-.4A11.907,11.907,0,0,0,21.75,11.537V3.923a1.487,1.487,0,0,0-.868-1.362A21.7,21.7,0,0,0,12,.75,21.7,21.7,0,0,0,3.118,2.561,1.487,1.487,0,0,0,2.25,3.923Z"/><rect class="a" x="7.5" y="9.75" width="9" height="7.5" rx="1.5" ry="1.5"/><path class="a" d="M12,5.25h0a3,3,0,0,0-3,3v1.5h6V8.25A3,3,0,0,0,12,5.25Z"/><path class="a" d="M12,13.154a.375.375,0,1,1-.375.375A.375.375,0,0,1,12,13.154"/></svg>
|
After Width: | Height: | Size: 703 B |
Loading…
Add table
Reference in a new issue