Ghost/ghost/admin/app/components/gh-launch-wizard/set-pricing.js
Rishabh Garg d16a8781a9 🐛 Fixed sending non-integer prices to tiers api (#2288)
closes https://github.com/TryGhost/Team/issues/1319

Due to how JS implements numbers, it's possible that when we multiple a number with 2 decimal places by 100 that we do not end up with an integer e.g. 9.95 * 100 = 994.999...

This is not a valid price for the API and so we must round it to the nearest integer. We round off prices both at source as well as in ties serializer to make sure we never send non integer prices to API.
2022-03-04 14:48:45 +05:30

214 lines
6.7 KiB
JavaScript

import Component from '@glimmer/component';
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';
import {task} from 'ember-concurrency';
import {tracked} from '@glimmer/tracking';
const CURRENCIES = currencies.map((currency) => {
return {
value: currency.isoCode.toLowerCase(),
label: `${currency.isoCode} - ${currency.name}`,
isoCode: currency.isoCode
};
});
export default class GhLaunchWizardSetPricingComponent extends Component {
@service config;
@service membersUtils;
@service settings;
@service store;
currencies = CURRENCIES;
@tracked stripeMonthlyAmount = 5;
@tracked stripeYearlyAmount = 50;
@tracked currency = 'usd';
@tracked isFreeChecked = true;
@tracked isMonthlyChecked = true;
@tracked isYearlyChecked = true;
@tracked stripePlanError = '';
@tracked product;
@tracked loadingProduct = false;
get selectedCurrency() {
return this.currencies.findBy('value', this.currency);
}
get allCurrencies() {
return getCurrencyOptions();
}
get isConnectDisallowed() {
const siteUrl = this.config.get('blogUrl');
return envConfig.environment !== 'development' && !/^https:/.test(siteUrl);
}
get isPaidPriceDisabled() {
return !this.membersUtils.isStripeEnabled;
}
get isFreeDisabled() {
return this.settings.get('membersSignupAccess') !== 'all';
}
willDestroy() {
super.willDestroy?.(...arguments);
// clear any unsaved settings changes when going back/forward/closing
this.args.updatePreview('');
}
@action
setup() {
this.fetchDefaultProduct.perform();
this.updatePreviewUrl();
}
@action
backStep() {
const product = this.product;
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,
currency: this.currency
});
this.args.backStep();
}
@action
setStripePlansCurrency(event) {
const newCurrency = event.value;
this.currency = newCurrency;
this.updatePreviewUrl();
}
@action
toggleFreePlan(event) {
this.isFreeChecked = event.target.checked;
this.updatePreviewUrl();
}
@action
toggleMonthlyPlan(event) {
this.isMonthlyChecked = event.target.checked;
this.updatePreviewUrl();
}
@action
toggleYearlyPlan(event) {
this.isYearlyChecked = event.target.checked;
this.updatePreviewUrl();
}
@action
validateStripePlans() {
this.stripePlanError = undefined;
try {
const yearlyAmount = this.stripeYearlyAmount;
const monthlyAmount = this.stripeMonthlyAmount;
const symbol = getSymbol(this.currency);
if (!yearlyAmount || yearlyAmount < 1 || !monthlyAmount || monthlyAmount < 1) {
throw new TypeError(`Subscription amount must be at least ${symbol}1.00`);
}
this.updatePreviewUrl();
} catch (err) {
this.stripePlanError = err.message;
}
}
@task
*saveAndContinue() {
if (this.isConnectDisallowed) {
this.args.nextStep();
} else {
yield this.validateStripePlans();
if (this.stripePlanError) {
return false;
}
const product = this.product;
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,
currency: this.currency
});
this.args.nextStep();
}
}
@task({drop: true})
*fetchDefaultProduct() {
const storedData = this.args.getData();
if (storedData?.product) {
this.product = storedData.product;
if (storedData.isMonthlyChecked !== undefined) {
this.isMonthlyChecked = storedData.isMonthlyChecked;
}
if (storedData.isYearlyChecked !== undefined) {
this.isYearlyChecked = storedData.isYearlyChecked;
}
if (storedData.isFreeChecked !== undefined) {
this.isFreeChecked = storedData.isFreeChecked;
}
if (storedData.currency !== undefined) {
this.currency = storedData.currency;
}
this.stripeMonthlyAmount = storedData.monthlyAmount;
this.stripeYearlyAmount = storedData.yearlyAmount;
} else {
const products = yield this.store.query('product', {filter: 'type:paid', include: 'monthly_price,yearly_price'});
this.product = products.firstObject;
let portalPlans = this.settings.get('portalPlans') || [];
this.isMonthlyChecked = portalPlans.includes('monthly');
this.isYearlyChecked = portalPlans.includes('yearly');
this.isFreeChecked = portalPlans.includes('free');
const monthlyPrice = this.product.get('monthlyPrice');
const yearlyPrice = this.product.get('yearlyPrice');
if (monthlyPrice && monthlyPrice.amount) {
this.stripeMonthlyAmount = (monthlyPrice.amount / 100);
this.currency = monthlyPrice.currency;
}
if (yearlyPrice && yearlyPrice.amount) {
this.stripeYearlyAmount = (yearlyPrice.amount / 100);
}
}
this.updatePreviewUrl();
}
updatePreviewUrl() {
const options = {
disableBackground: true,
currency: this.selectedCurrency.value,
monthlyPrice: Math.round(this.stripeMonthlyAmount * 100),
yearlyPrice: Math.round(this.stripeYearlyAmount * 100),
isMonthlyChecked: this.isMonthlyChecked,
isYearlyChecked: this.isYearlyChecked,
isFreeChecked: this.isFreeChecked,
portalPlans: null
};
const url = this.membersUtils.getPortalPreviewUrl(options);
this.args.updatePreview(url);
}
}