mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-23 10:53:34 +03:00
Updated usage of the Tiers API (#2388)
refs https://github.com/TryGhost/Team/issues/1575 - Update usage of Tier to read monthly & yearly price & currency from top level - Updated usage of Tier to read benefit name from benefits[n], not from benefits[n].name Co-authored-by: Fabien "egg" O'Carroll <fabien@allou.is>
This commit is contained in:
parent
77484210ee
commit
8b5b3aa734
@ -1177,6 +1177,11 @@ remove|ember-template-lint|no-action|78|55|78|55|856c8ab1d5835ec424c83eb6b73888b
|
||||
remove|ember-template-lint|no-action|82|59|82|59|e8badded2afd5d0c8426b85b6d0e7ac0bef55efb|1652054400000|1662422400000|1665014400000|app/templates/settings/integrations/slack.hbs
|
||||
remove|ember-template-lint|no-passed-in-event-handlers|52|48|52|48|a893d9d149388aa9da024f59db6522bb9335bf8a|1652054400000|1662422400000|1665014400000|app/templates/settings/integrations/slack.hbs
|
||||
remove|ember-template-lint|no-passed-in-event-handlers|78|48|78|48|1ab458a0888727af2b3a7a661a1e7d5f00ac0ad5|1652054400000|1662422400000|1665014400000|app/templates/settings/integrations/slack.hbs
|
||||
remove|ember-template-lint|no-action|206|59|206|59|dcc09bb23a476d5b83b273b693cd8cb2aba68365|1652054400000|1662422400000|1665014400000|app/templates/settings/membership.hbs
|
||||
remove|ember-template-lint|no-action|207|63|207|63|682c80ff5163c8e4a2bea89f2066dfd1248b5889|1652054400000|1662422400000|1665014400000|app/templates/settings/membership.hbs
|
||||
remove|ember-template-lint|no-action|216|59|216|59|a80dd18e18dda6fb6f1f97d87bef2b8c2ce3d847|1652054400000|1662422400000|1665014400000|app/templates/settings/membership.hbs
|
||||
remove|ember-template-lint|no-passed-in-event-handlers|206|52|206|52|dcb4785647a50814bcfce82f8d68ac8dd8f54ec2|1652054400000|1662422400000|1665014400000|app/templates/settings/membership.hbs
|
||||
remove|ember-template-lint|no-passed-in-event-handlers|216|52|216|52|70487c008d7dda453fef82f0140699ee93c0055c|1652054400000|1662422400000|1665014400000|app/templates/settings/membership.hbs
|
||||
remove|ember-template-lint|no-action|59|49|59|49|caf96338bbfda1006cc9251eacd9d9eee883df44|1652054400000|1662422400000|1665014400000|app/templates/editor.hbs
|
||||
remove|ember-template-lint|no-action|60|44|60|44|1731ed4d2ac62556bb83fbc378686e26ca605687|1652054400000|1662422400000|1665014400000|app/templates/editor.hbs
|
||||
add|ember-template-lint|table-groups|29|12|29|12|49e0fad85b643b6129963e49b6b5a37ccbd9796c|1652659200000|1663027200000|1668214800000|app/templates/offers.hbs
|
||||
|
@ -2,7 +2,6 @@ import ApplicationAdapter from 'ghost-admin/adapters/application';
|
||||
import classic from 'ember-classic-decorator';
|
||||
|
||||
@classic
|
||||
|
||||
export default class Tier extends ApplicationAdapter {
|
||||
queryRecord(store, type, query) {
|
||||
if (query && query.id) {
|
||||
|
@ -165,25 +165,9 @@ export default class GhLaunchWizardConnectStripeComponent extends Component {
|
||||
this.tier = tiers.firstObject;
|
||||
|
||||
if (this.tier) {
|
||||
const yearlyDiscount = this.calculateDiscount(5, 50);
|
||||
this.tier.set('monthlyPrice', {
|
||||
nickname: 'Monthly',
|
||||
amount: 500,
|
||||
active: true,
|
||||
description: 'Full access',
|
||||
currency: 'usd',
|
||||
interval: 'month',
|
||||
type: 'recurring'
|
||||
});
|
||||
this.tier.set('yearlyPrice', {
|
||||
nickname: 'Yearly',
|
||||
amount: 5000,
|
||||
active: true,
|
||||
currency: 'usd',
|
||||
description: yearlyDiscount > 0 ? `${yearlyDiscount}% discount` : 'Full access',
|
||||
interval: 'year',
|
||||
type: 'recurring'
|
||||
});
|
||||
this.tier.set('currency', 'usd');
|
||||
this.tier.set('monthlyPrice', 500);
|
||||
this.tier.set('yearlyPrice', 5000);
|
||||
yield this.saveTier.perform();
|
||||
this.settings.set('portalPlans', ['free', 'monthly', 'yearly']);
|
||||
yield this.settings.save();
|
||||
|
@ -22,24 +22,9 @@ export default class GhLaunchWizardFinaliseComponent extends Component {
|
||||
const monthlyAmount = Math.round(data.monthlyAmount * 100);
|
||||
const yearlyAmount = Math.round(data.yearlyAmount * 100);
|
||||
const currency = data.currency;
|
||||
const monthlyPrice = {
|
||||
nickname: 'Monthly',
|
||||
amount: monthlyAmount,
|
||||
active: true,
|
||||
currency: currency,
|
||||
interval: 'month',
|
||||
type: 'recurring'
|
||||
};
|
||||
const yearlyPrice = {
|
||||
nickname: 'Yearly',
|
||||
amount: yearlyAmount,
|
||||
active: true,
|
||||
currency: currency,
|
||||
interval: 'year',
|
||||
type: 'recurring'
|
||||
};
|
||||
this.tier.set('monthlyPrice', monthlyPrice);
|
||||
this.tier.set('yearlyPrice', yearlyPrice);
|
||||
this.tier.set('monthlyPrice', monthlyAmount);
|
||||
this.tier.set('yearlyPrice', yearlyAmount);
|
||||
this.tier.set('currency', currency);
|
||||
const savedTier = await this.tier.save();
|
||||
return savedTier;
|
||||
}
|
||||
|
@ -233,47 +233,13 @@ export default Component.extend({
|
||||
this.onDisconnected?.();
|
||||
}),
|
||||
|
||||
calculateDiscount(monthly, yearly) {
|
||||
if (isNaN(monthly) || isNaN(yearly)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return monthly ? 100 - Math.floor((yearly / 12 * 100) / monthly) : 0;
|
||||
},
|
||||
|
||||
getActivePrice(prices, interval, amount, currency) {
|
||||
return prices.find((price) => {
|
||||
return (
|
||||
price.active && price.amount === amount && price.type === 'recurring' &&
|
||||
price.interval === interval && price.currency.toLowerCase() === currency.toLowerCase()
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
saveTier: task(function* () {
|
||||
const tiers = yield this.store.query('tier', {filter: 'type:paid', include: 'monthly_price, yearly_price'});
|
||||
this.tier = tiers.firstObject;
|
||||
if (this.tier) {
|
||||
const yearlyDiscount = this.calculateDiscount(5, 50);
|
||||
this.tier.set('monthlyPrice', {
|
||||
nickname: 'Monthly',
|
||||
amount: 500,
|
||||
active: true,
|
||||
description: 'Full access',
|
||||
currency: 'usd',
|
||||
interval: 'month',
|
||||
type: 'recurring'
|
||||
});
|
||||
this.tier.set('yearlyPrice', {
|
||||
nickname: 'Yearly',
|
||||
amount: 5000,
|
||||
active: true,
|
||||
currency: 'usd',
|
||||
description: yearlyDiscount > 0 ? `${yearlyDiscount}% discount` : 'Full access',
|
||||
interval: 'year',
|
||||
type: 'recurring'
|
||||
});
|
||||
|
||||
this.tier.set('monthlyPrice', 500);
|
||||
this.tier.set('yearlyPrice', 5000);
|
||||
this.tier.set('currency', 'usd');
|
||||
let pollTimeout = 0;
|
||||
/** To allow Stripe config to be ready in backend, we poll the save tier request */
|
||||
while (pollTimeout < RETRY_PRODUCT_SAVE_MAX_POLL) {
|
||||
|
@ -41,14 +41,14 @@
|
||||
<div class="gh-tier-card-price" data-test-monthly-price>
|
||||
<div class="flex items-start">
|
||||
<span class="currency">{{currency-symbol this.tierCurrency}}</span>
|
||||
<span class="amount">{{gh-price-amount @tier.monthlyPrice.amount}}</span>
|
||||
<span class="amount">{{gh-price-amount @tier.monthlyPrice}}</span>
|
||||
</div>
|
||||
<div class="period">Monthly</div>
|
||||
</div>
|
||||
<div class="gh-tier-card-price" data-test-yearly-price>
|
||||
<div class="flex items-start">
|
||||
<span class="currency">{{currency-symbol this.tierCurrency}}</span>
|
||||
<span class="amount">{{gh-price-amount @tier.yearlyPrice.amount}}</span>
|
||||
<span class="amount">{{gh-price-amount @tier.yearlyPrice}}</span>
|
||||
</div>
|
||||
<div class="period">Yearly</div>
|
||||
</div>
|
||||
|
@ -26,9 +26,9 @@ export default class extends Component {
|
||||
const firstPaidTier = this.args.tiers.find((tier) => {
|
||||
return tier.type === 'paid';
|
||||
});
|
||||
return firstPaidTier?.monthlyPrice?.currency || 'usd';
|
||||
return firstPaidTier?.currency || 'usd';
|
||||
} else {
|
||||
return this.tier?.monthlyPrice?.currency;
|
||||
return this.tier?.currency;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,89 +0,0 @@
|
||||
<header class="modal-header" data-test-modal="webhook-form">
|
||||
<h1 data-test-text="title">{{this.title}}</h1>
|
||||
</header>
|
||||
<button class="close" href title="Close" type="button" {{action "closeModal"}} {{action (optional this.noop) on="mouseDown"}}>
|
||||
{{svg-jar "close"}}
|
||||
</button>
|
||||
|
||||
<form>
|
||||
<div class="modal-body">
|
||||
<div class="gh-main-section-block">
|
||||
<div class="gh-main-section-content grey gh-tier-priceform-block">
|
||||
<GhFormGroup @errors={{this.errors}} @property="name">
|
||||
<label for="name" class="fw6">Name</label>
|
||||
<GhTextInput
|
||||
@value={{readonly this.price.nickname}}
|
||||
@input={{action (mut this.price.nickname) value="target.value"}}
|
||||
@name="name"
|
||||
@id="name"
|
||||
@class="gh-input" />
|
||||
<GhErrorMessage @errors={{this.errors}} @property="name" />
|
||||
</GhFormGroup>
|
||||
<GhFormGroup @errors={{this.errors}} @property="description">
|
||||
<label for="description" class="fw6">Description</label>
|
||||
<GhTextInput
|
||||
@value={{readonly this.price.description}}
|
||||
@input={{action (mut this.price.description) value="target.value"}}
|
||||
@name="description"
|
||||
@id="description"
|
||||
@class="gh-input" />
|
||||
<GhErrorMessage @errors={{this.errors}} @property="description" />
|
||||
</GhFormGroup>
|
||||
<div class="gh-tier-priceform-pricecurrency">
|
||||
<GhFormGroup @errors={{this.errors}} @property="amount">
|
||||
<label for="amount" class="fw6">Price</label>
|
||||
<div class="flex items-center justify-center gh-labs-price-label">
|
||||
<GhTextInput
|
||||
@id="amount"
|
||||
@value={{this.price.amount}}
|
||||
@type="number"
|
||||
@disabled={{this.isExistingPrice}}
|
||||
@input={{action "setAmount" value="target.value"}}
|
||||
/>
|
||||
</div>
|
||||
<GhErrorMessage @errors={{this.errors}} @property="amount" />
|
||||
</GhFormGroup>
|
||||
<GhFormGroup @class="for-select">
|
||||
<label class="fw6 f8"for="currency">Plan currency</label>
|
||||
<span class="gh-select mt1">
|
||||
{{one-way-select this.selectedCurrencyObj
|
||||
id="currency"
|
||||
name="currency"
|
||||
options=(readonly this.allCurrencies)
|
||||
optionValuePath="value"
|
||||
optionLabelPath="label"
|
||||
disabled=this.isExistingPrice
|
||||
update=(action "setCurrency")
|
||||
}}
|
||||
{{svg-jar "arrow-down-small"}}
|
||||
</span>
|
||||
</GhFormGroup>
|
||||
</div>
|
||||
<GhFormGroup @errors={{this.price.errors}} @hasValidated={{this.price.hasValidated}} @property="billing-period">
|
||||
<label for="billing-period" class="fw6">Billing period</label>
|
||||
<GhTiersPriceBillingperiod
|
||||
@updatePeriod={{action "updatePeriod"}}
|
||||
@triggerId="period-input"
|
||||
@value={{this.price.interval}} @disabled={{this.isExistingPrice}}
|
||||
/>
|
||||
<GhErrorMessage @errors={{this.errors}} @property="interval" />
|
||||
</GhFormGroup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
class="gh-btn" data-test-button="cancel-webhook" type="button" {{action "closeModal"}}
|
||||
{{!-- disable mouseDown so it doesn't trigger focus-out validations --}}
|
||||
{{action (optional this.noop) on="mouseDown"}}
|
||||
>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
<GhTaskButton @buttonText="Save"
|
||||
@successText={{this.successText}}
|
||||
@task={{this.savePrice}}
|
||||
@class="gh-btn gh-btn-black gh-btn-icon"
|
||||
data-test-button="save-price" />
|
||||
</div>
|
@ -1,143 +0,0 @@
|
||||
import EmberObject, {action} from '@ember/object';
|
||||
import ModalBase from 'ghost-admin/components/modal-base';
|
||||
import classic from 'ember-classic-decorator';
|
||||
import {currencies} from 'ghost-admin/utils/currency';
|
||||
import {isEmpty} from '@ember/utils';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
// TODO: update modals to work fully with Glimmer components
|
||||
@classic
|
||||
export default class ModalTierPrice extends ModalBase {
|
||||
@tracked model;
|
||||
@tracked price;
|
||||
@tracked currencyVal;
|
||||
@tracked periodVal;
|
||||
@tracked errors = EmberObject.create();
|
||||
|
||||
init() {
|
||||
super.init(...arguments);
|
||||
this.price = {
|
||||
...(this.model.price || {})
|
||||
};
|
||||
this.topCurrencies = currencies.slice(0, 5).map((currency) => {
|
||||
return {
|
||||
value: currency.isoCode.toLowerCase(),
|
||||
label: `${currency.isoCode} - ${currency.name}`,
|
||||
isoCode: currency.isoCode
|
||||
};
|
||||
});
|
||||
this.currencies = currencies.slice(5, currencies.length).map((currency) => {
|
||||
return {
|
||||
value: currency.isoCode.toLowerCase(),
|
||||
label: `${currency.isoCode} - ${currency.name}`,
|
||||
isoCode: currency.isoCode
|
||||
};
|
||||
});
|
||||
this.allCurrencies = [
|
||||
{
|
||||
groupName: '—',
|
||||
options: this.topCurrencies
|
||||
},
|
||||
{
|
||||
groupName: '—',
|
||||
options: this.currencies
|
||||
}
|
||||
];
|
||||
this.currencyVal = this.price.currency || 'usd';
|
||||
this.periodVal = this.price.interval || 'month';
|
||||
}
|
||||
|
||||
get title() {
|
||||
if (this.isExistingPrice) {
|
||||
return `Price - ${this.price.nickname || 'No Name'}`;
|
||||
}
|
||||
return 'New Price';
|
||||
}
|
||||
|
||||
get isExistingPrice() {
|
||||
return !!this.model.price;
|
||||
}
|
||||
|
||||
get currency() {
|
||||
return this.price.currency || 'usd';
|
||||
}
|
||||
|
||||
get selectedCurrencyObj() {
|
||||
return this.currencies.findBy('value', this.price.currency) || this.topCurrencies.findBy('value', this.price.currency);
|
||||
}
|
||||
|
||||
// TODO: rename to confirm() when modals have full Glimmer support
|
||||
@action
|
||||
confirmAction() {
|
||||
this.confirm(this.price);
|
||||
this.close();
|
||||
}
|
||||
|
||||
@action
|
||||
close(event) {
|
||||
event?.preventDefault?.();
|
||||
this.closeModal();
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
*savePrice() {
|
||||
this.validatePriceData();
|
||||
if (!isEmpty(this.errors) && Object.keys(this.errors).length > 0) {
|
||||
return;
|
||||
}
|
||||
const priceObj = {
|
||||
...this.price,
|
||||
amount: (this.price.amount || 0) * 100
|
||||
};
|
||||
if (!priceObj.id) {
|
||||
priceObj.active = 1;
|
||||
priceObj.currency = priceObj.currency || 'usd';
|
||||
priceObj.interval = priceObj.interval || 'month';
|
||||
priceObj.type = 'recurring';
|
||||
}
|
||||
yield this.confirm(priceObj);
|
||||
this.send('closeModal');
|
||||
}
|
||||
|
||||
validatePriceData() {
|
||||
this.errors = EmberObject.create();
|
||||
if (!this.price.nickname) {
|
||||
this.errors.set('name', [{
|
||||
message: 'Please enter name'
|
||||
}]);
|
||||
}
|
||||
if (isNaN(this.price.amount) || this.price.amount === '') {
|
||||
this.errors.set('amount', [{
|
||||
message: 'Please enter amount'
|
||||
}]);
|
||||
}
|
||||
if (!this.price.interval || !['month', 'year'].includes(this.price.interval)) {
|
||||
this.errors.set('interval', [{
|
||||
message: 'Please enter billing interval'
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
actions = {
|
||||
confirm() {
|
||||
this.confirmAction(...arguments);
|
||||
},
|
||||
updatePeriod(oldPeriod, newPeriod) {
|
||||
this.price.interval = newPeriod;
|
||||
this.periodVal = newPeriod;
|
||||
},
|
||||
setAmount(amount) {
|
||||
this.price.amount = !isNaN(amount) ? parseInt(amount) : 0;
|
||||
},
|
||||
|
||||
setCurrency(currency) {
|
||||
this.price.currency = currency.value;
|
||||
this.currencyVal = currency.value;
|
||||
},
|
||||
// needed because ModalBase uses .send() for keyboard events
|
||||
closeModal() {
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
}
|
@ -36,6 +36,8 @@ export default class ModalTierPrice extends ModalBase {
|
||||
@tracked welcomePageURL;
|
||||
@tracked previewCadence = 'yearly';
|
||||
@tracked discountValue = 0;
|
||||
@tracked hasSaved = false;
|
||||
@tracked savedBenefits;
|
||||
|
||||
accentColorStyle = '';
|
||||
|
||||
@ -49,17 +51,6 @@ export default class ModalTierPrice extends ModalBase {
|
||||
return getCurrencyOptions();
|
||||
}
|
||||
|
||||
get tierCurrency() {
|
||||
if (this.isFreeTier) {
|
||||
const firstPaidTier = this.model.tiers?.find((tier) => {
|
||||
return tier.type === 'paid';
|
||||
});
|
||||
return firstPaidTier?.monthlyPrice?.currency || 'usd';
|
||||
} else {
|
||||
return this.tier?.monthlyPrice?.currency;
|
||||
}
|
||||
}
|
||||
|
||||
get selectedCurrency() {
|
||||
return CURRENCIES.findBy('value', this.currency);
|
||||
}
|
||||
@ -67,15 +58,16 @@ export default class ModalTierPrice extends ModalBase {
|
||||
init() {
|
||||
super.init(...arguments);
|
||||
this.tier = this.model.tier;
|
||||
this.savedBenefits = this.model.tier?.get('benefits');
|
||||
const monthlyPrice = this.tier.get('monthlyPrice');
|
||||
const yearlyPrice = this.tier.get('yearlyPrice');
|
||||
if (monthlyPrice) {
|
||||
this.stripeMonthlyAmount = (monthlyPrice.amount / 100);
|
||||
this.stripeMonthlyAmount = (monthlyPrice / 100);
|
||||
}
|
||||
if (yearlyPrice) {
|
||||
this.stripeYearlyAmount = (yearlyPrice.amount / 100);
|
||||
this.stripeYearlyAmount = (yearlyPrice / 100);
|
||||
}
|
||||
this.currency = this.tierCurrency || 'usd';
|
||||
this.currency = this.tier.get('currency') || 'usd';
|
||||
this.benefits = this.tier.get('benefits') || emberA([]);
|
||||
this.newBenefit = TierBenefitItem.create({
|
||||
isNew: true,
|
||||
@ -128,7 +120,9 @@ export default class ModalTierPrice extends ModalBase {
|
||||
|
||||
@action
|
||||
close(event) {
|
||||
this.reset();
|
||||
if (!this.hasSaved) {
|
||||
this.reset();
|
||||
}
|
||||
event?.preventDefault?.();
|
||||
this.closeModal();
|
||||
}
|
||||
@ -144,8 +138,8 @@ export default class ModalTierPrice extends ModalBase {
|
||||
|
||||
reset() {
|
||||
this.newBenefit = TierBenefitItem.create({isNew: true, name: ''});
|
||||
const savedBenefits = this.tier.benefits?.filter(benefit => !!benefit.id) || emberA([]);
|
||||
this.tier.set('benefits', savedBenefits);
|
||||
const finalBenefits = this.savedBenefits || emberA([]);
|
||||
this.tier.set('benefits', finalBenefits);
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
@ -165,26 +159,13 @@ export default class ModalTierPrice extends ModalBase {
|
||||
if (!this.isFreeTier) {
|
||||
const monthlyAmount = Math.round(this.stripeMonthlyAmount * 100);
|
||||
const yearlyAmount = Math.round(this.stripeYearlyAmount * 100);
|
||||
this.tier.set('monthlyPrice', {
|
||||
nickname: 'Monthly',
|
||||
amount: monthlyAmount,
|
||||
active: true,
|
||||
currency: this.currency,
|
||||
interval: 'month',
|
||||
type: 'recurring'
|
||||
});
|
||||
this.tier.set('yearlyPrice', {
|
||||
nickname: 'Yearly',
|
||||
amount: yearlyAmount,
|
||||
active: true,
|
||||
currency: this.currency,
|
||||
interval: 'year',
|
||||
type: 'recurring'
|
||||
});
|
||||
this.tier.set('monthlyPrice', monthlyAmount);
|
||||
this.tier.set('yearlyPrice', yearlyAmount);
|
||||
this.tier.set('currency', this.currency);
|
||||
}
|
||||
this.tier.set('benefits', this.benefits.filter(benefit => !benefit.get('isBlank')));
|
||||
yield this.tier.save();
|
||||
|
||||
this.hasSaved = true;
|
||||
yield this.confirm();
|
||||
this.send('closeModal');
|
||||
}
|
||||
|
@ -85,10 +85,10 @@ export default class OffersController extends Controller {
|
||||
get cadence() {
|
||||
if (this.offer.tier && this.offer.cadence) {
|
||||
const tier = this.tiers.findBy('id', this.offer.tier.id);
|
||||
return `${this.offer.tier.id}-${this.offer.cadence}-${tier?.monthlyPrice?.currency}`;
|
||||
return `${this.offer.tier.id}-${this.offer.cadence}-${tier?.currency}`;
|
||||
} else if (this.defaultProps) {
|
||||
const tier = this.tiers.findBy('id', this.defaultProps.tier.id);
|
||||
return `${this.defaultProps.tier.id}-${this.defaultProps.cadence}-${tier?.monthlyPrice?.currency}`;
|
||||
return `${this.defaultProps.tier.id}-${this.defaultProps.cadence}-${tier?.currency}`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@ -109,10 +109,10 @@ export default class OffersController extends Controller {
|
||||
this.tiers.forEach((tier) => {
|
||||
let monthlyLabel;
|
||||
let yearlyLabel;
|
||||
const tierCurrency = tier.monthlyPrice.currency;
|
||||
const tierCurrency = tier.currency;
|
||||
const tierCurrencySymbol = tierCurrency.toUpperCase();
|
||||
monthlyLabel = `${tier.name} - Monthly (${ghPriceAmount(tier.monthlyPrice.amount)} ${tierCurrencySymbol})`;
|
||||
yearlyLabel = `${tier.name} - Yearly (${ghPriceAmount(tier.yearlyPrice.amount)} ${tierCurrencySymbol})`;
|
||||
monthlyLabel = `${tier.name} - Monthly (${ghPriceAmount(tier.monthlyPrice)} ${tierCurrencySymbol})`;
|
||||
yearlyLabel = `${tier.name} - Yearly (${ghPriceAmount(tier.yearlyPrice)} ${tierCurrencySymbol})`;
|
||||
|
||||
cadences.push({
|
||||
label: monthlyLabel,
|
||||
@ -374,7 +374,7 @@ export default class OffersController extends Controller {
|
||||
return '$';
|
||||
}
|
||||
const tier = this.tiers.findBy('id', tierId);
|
||||
const tierCurrency = tier?.monthlyPrice?.currency || 'usd';
|
||||
const tierCurrency = tier?.currency || 'usd';
|
||||
return getSymbol(tierCurrency);
|
||||
}
|
||||
|
||||
|
@ -42,9 +42,9 @@ export default class MembersController extends Controller {
|
||||
return p.id === offer.tier.id;
|
||||
});
|
||||
const price = offer.cadence === 'month' ? tier.monthlyPrice : tier.yearlyPrice;
|
||||
offer.finalCurrency = offer.currency || price.currency;
|
||||
offer.originalPrice = price.amount;
|
||||
offer.updatedPrice = offer.type === 'fixed' ? (price.amount - offer.amount) : (price.amount - ((price.amount * offer.amount) / 100));
|
||||
offer.finalCurrency = offer.currency || tier.currency;
|
||||
offer.originalPrice = price;
|
||||
offer.updatedPrice = offer.type === 'fixed' ? (price - offer.amount) : (price - ((price * offer.amount) / 100));
|
||||
return offer;
|
||||
});
|
||||
}
|
||||
|
@ -339,12 +339,12 @@ export default class MembersAccessController extends Controller {
|
||||
if (tier) {
|
||||
const monthlyPrice = tier.get('monthlyPrice');
|
||||
const yearlyPrice = tier.get('yearlyPrice');
|
||||
if (monthlyPrice && monthlyPrice.amount) {
|
||||
this.stripeMonthlyAmount = (monthlyPrice.amount / 100);
|
||||
this.currency = monthlyPrice.currency;
|
||||
this.currency = tier.get('currency');
|
||||
if (monthlyPrice) {
|
||||
this.stripeMonthlyAmount = (monthlyPrice / 100);
|
||||
}
|
||||
if (yearlyPrice && yearlyPrice.amount) {
|
||||
this.stripeYearlyAmount = (yearlyPrice.amount / 100);
|
||||
if (yearlyPrice) {
|
||||
this.stripeYearlyAmount = (yearlyPrice / 100);
|
||||
}
|
||||
this.updatePortalPreview();
|
||||
}
|
||||
@ -380,22 +380,8 @@ export default class MembersAccessController extends Controller {
|
||||
const monthlyAmount = Math.round(this.stripeMonthlyAmount * 100);
|
||||
const yearlyAmount = Math.round(this.stripeYearlyAmount * 100);
|
||||
|
||||
this.tier.set('monthlyPrice', {
|
||||
nickname: 'Monthly',
|
||||
amount: monthlyAmount,
|
||||
active: true,
|
||||
currency: this.currency,
|
||||
interval: 'month',
|
||||
type: 'recurring'
|
||||
});
|
||||
this.tier.set('yearlyPrice', {
|
||||
nickname: 'Yearly',
|
||||
amount: yearlyAmount,
|
||||
active: true,
|
||||
currency: this.currency,
|
||||
interval: 'year',
|
||||
type: 'recurring'
|
||||
});
|
||||
this.tier.set('monthlyPrice', monthlyAmount);
|
||||
this.tier.set('yearlyPrice', yearlyAmount);
|
||||
|
||||
const savedTier = await this.tier.save();
|
||||
return savedTier;
|
||||
|
@ -1,13 +0,0 @@
|
||||
import EmberObject from '@ember/object';
|
||||
|
||||
export default EmberObject.extend({
|
||||
id: 'ID in Ghost',
|
||||
stripe_price_id: 'ID of the Stripe Price',
|
||||
stripe_product_id: 'ID of the Stripe Product the Stripe Price is associated with',
|
||||
nickname: 'price nickname e.g. "Monthly"',
|
||||
description: 'price description e.g. "Full access"',
|
||||
amount: 'amount in smallest denomination e.g. cents, so value for 5 dollars would be 500',
|
||||
currency: 'e.g. usd',
|
||||
type: 'either one_time or recurring',
|
||||
interval: 'will be `null` if type is one_time, otherwise how often price charges e.g "month", "year"'
|
||||
});
|
@ -11,7 +11,8 @@ export default Model.extend(ValidationEngine, {
|
||||
welcomePageURL: attr('string'),
|
||||
visibility: attr('string', {defaultValue: 'none'}),
|
||||
type: attr('string', {defaultValue: 'paid'}),
|
||||
monthlyPrice: attr('stripe-price'),
|
||||
yearlyPrice: attr('stripe-price'),
|
||||
currency: attr('string'),
|
||||
monthlyPrice: attr('number'),
|
||||
yearlyPrice: attr('number'),
|
||||
benefits: attr('tier-benefits')
|
||||
});
|
||||
|
@ -4,12 +4,12 @@ export default class TierSerializer extends ApplicationSerializer {
|
||||
serialize() {
|
||||
let json = super.serialize(...arguments);
|
||||
|
||||
if (json?.monthly_price?.amount) {
|
||||
json.monthly_price.amount = Math.round(json.monthly_price.amount);
|
||||
if (json?.monthly_price) {
|
||||
json.monthly_price = Math.round(json.monthly_price);
|
||||
}
|
||||
|
||||
if (json?.yearly_price?.amount) {
|
||||
json.yearly_price.amount = Math.round(json.yearly_price.amount);
|
||||
if (json?.yearly_price) {
|
||||
json.yearly_price = Math.round(json.yearly_price);
|
||||
}
|
||||
|
||||
return json;
|
||||
|
@ -1,27 +0,0 @@
|
||||
import StripePrice from 'ghost-admin/models/stripe-price';
|
||||
import Transform from '@ember-data/serializer/transform';
|
||||
import {A as emberA, isArray as isEmberArray} from '@ember/array';
|
||||
|
||||
export default class StripePriceTransform extends Transform {
|
||||
deserialize(serialized) {
|
||||
if (serialized === null || serialized === undefined) {
|
||||
return null;
|
||||
} else if (Array.isArray(serialized)) {
|
||||
const stripePrices = serialized.map(itemDetails => StripePrice.create(itemDetails));
|
||||
|
||||
return emberA(stripePrices);
|
||||
} else {
|
||||
return StripePrice.create(serialized);
|
||||
}
|
||||
}
|
||||
|
||||
serialize(deserialized) {
|
||||
if (isEmberArray(deserialized)) {
|
||||
return deserialized.map((item) => {
|
||||
return item;
|
||||
}).compact();
|
||||
} else {
|
||||
return deserialized || null;
|
||||
}
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ export default class TierBenefits extends Transform {
|
||||
benefitsArray = serialized || [];
|
||||
|
||||
benefitsItems = benefitsArray.map((itemDetails) => {
|
||||
return TierBenefitItem.create(itemDetails);
|
||||
return TierBenefitItem.create({name: itemDetails});
|
||||
});
|
||||
|
||||
return emberA(benefitsItems);
|
||||
@ -21,7 +21,7 @@ export default class TierBenefits extends Transform {
|
||||
if (isEmberArray(deserialized)) {
|
||||
benefitsArray = deserialized.map((item) => {
|
||||
let name = item.get('name').trim();
|
||||
return {name};
|
||||
return name;
|
||||
}).compact();
|
||||
} else {
|
||||
benefitsArray = [];
|
||||
|
@ -17,26 +17,12 @@ export default function mockTiers(server) {
|
||||
});
|
||||
});
|
||||
|
||||
server.put('/tiers/:id/', function ({tiers, tierBenefits}, {params}) {
|
||||
server.put('/tiers/:id/', function ({tiers}, {params}) {
|
||||
const attrs = this.normalizedRequestAttrs();
|
||||
const tier = tiers.find(params.id);
|
||||
|
||||
const benefitAttrs = attrs.benefits;
|
||||
delete attrs.benefits;
|
||||
|
||||
tier.update(attrs);
|
||||
|
||||
benefitAttrs.forEach((benefit) => {
|
||||
if (benefit.id) {
|
||||
const tierBenefit = tierBenefits.find(benefit.id);
|
||||
tierBenefit.tier = tier;
|
||||
tierBenefit.save();
|
||||
} else {
|
||||
tier.createTierBenefit(benefit);
|
||||
tier.save();
|
||||
}
|
||||
});
|
||||
|
||||
return tier.save();
|
||||
});
|
||||
|
||||
|
@ -7,20 +7,7 @@ export default Factory.extend({
|
||||
slug(i) { return `tier-${i}`;},
|
||||
type: 'paid',
|
||||
visibility: 'none',
|
||||
monthly_price() {
|
||||
return {
|
||||
interval: 'month',
|
||||
nickname: 'Monthly',
|
||||
currency: 'usd',
|
||||
amount: 500
|
||||
};
|
||||
},
|
||||
yearly_price() {
|
||||
return {
|
||||
interval: 'year',
|
||||
nickname: 'Yearly',
|
||||
currency: 'usd',
|
||||
amount: 5000
|
||||
};
|
||||
}
|
||||
currency: 'usd',
|
||||
monthly_price: 500,
|
||||
yearly_price: 5000
|
||||
});
|
||||
|
@ -1,5 +0,0 @@
|
||||
import {Model, belongsTo} from 'miragejs';
|
||||
|
||||
export default Model.extend({
|
||||
tier: belongsTo('tier')
|
||||
});
|
@ -1,8 +1,5 @@
|
||||
import {Model, hasMany} from 'miragejs';
|
||||
|
||||
export default Model.extend({
|
||||
// ran into odd relationship bugs when called `benefits`
|
||||
// serializer will rename to `benefits`
|
||||
tierBenefits: hasMany(),
|
||||
members: hasMany()
|
||||
});
|
||||
|
@ -1,3 +0,0 @@
|
||||
import BaseSerializer from './application';
|
||||
|
||||
export default BaseSerializer.extend({});
|
@ -286,8 +286,7 @@ describe('Acceptance: Settings - Membership', function () {
|
||||
const freeTier = this.server.db.tiers.findBy({slug: 'free'});
|
||||
expect(freeTier.description).to.equal('Test description');
|
||||
expect(freeTier.welcomePageUrl).to.equal('/not%20a%20url');
|
||||
expect(freeTier.tierBenefitIds.length).to.equal(1);
|
||||
const benefits = this.server.db.tierBenefits.find(freeTier.tierBenefitIds);
|
||||
expect(benefits[0].name).to.equal('Second benefit');
|
||||
expect(freeTier.benefits.length).to.equal(1);
|
||||
expect(freeTier.benefits[0]).to.equal('Second benefit');
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user