mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-18 16:01:40 +03:00
Handled updated tiers API data structure (#247)
refs https://github.com/TryGhost/Team/issues/1575 - updated to work with new tiers api data structure which removes stripe id references
This commit is contained in:
parent
7699424cee
commit
b306b0747d
@ -1,4 +1,4 @@
|
||||
import {createPopupNotification, getMemberEmail, getMemberName, removePortalLinkFromUrl} from './utils/helpers';
|
||||
import {createPopupNotification, getMemberEmail, getMemberName, getProductCadenceFromPrice, removePortalLinkFromUrl} from './utils/helpers';
|
||||
|
||||
function switchPage({data, state}) {
|
||||
return {
|
||||
@ -99,7 +99,8 @@ async function signup({data, state, api}) {
|
||||
if (plan.toLowerCase() === 'free') {
|
||||
await api.member.sendMagicLink(data);
|
||||
} else {
|
||||
await api.member.checkoutPlan({plan, email, name, newsletters, offerId});
|
||||
const {tierId, cadence} = getProductCadenceFromPrice({site: state?.site, priceId: plan});
|
||||
await api.member.checkoutPlan({plan, tierId, cadence, email, name, newsletters, offerId});
|
||||
}
|
||||
return {
|
||||
page: 'magiclink',
|
||||
@ -119,8 +120,11 @@ async function signup({data, state, api}) {
|
||||
async function checkoutPlan({data, state, api}) {
|
||||
try {
|
||||
const {plan, offerId} = data;
|
||||
const {tierId, cadence} = getProductCadenceFromPrice({site: state?.site, priceId: plan});
|
||||
await api.member.checkoutPlan({
|
||||
plan,
|
||||
tierId,
|
||||
cadence,
|
||||
offerId,
|
||||
metadata: {
|
||||
checkoutType: 'upgrade'
|
||||
@ -140,8 +144,15 @@ async function checkoutPlan({data, state, api}) {
|
||||
async function updateSubscription({data, state, api}) {
|
||||
try {
|
||||
const {plan, planId, subscriptionId, cancelAtPeriodEnd} = data;
|
||||
const {tierId, cadence} = getProductCadenceFromPrice({site: state?.site, priceId: planId});
|
||||
|
||||
await api.member.updateSubscription({
|
||||
planName: plan, subscriptionId, cancelAtPeriodEnd, planId: planId
|
||||
planName: plan,
|
||||
tierId,
|
||||
cadence,
|
||||
subscriptionId,
|
||||
cancelAtPeriodEnd,
|
||||
planId: planId
|
||||
});
|
||||
const member = await api.member.sessionData();
|
||||
const action = 'updateSubscription:success';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, {useContext, useEffect, useState} from 'react';
|
||||
import {ReactComponent as LoaderIcon} from '../../images/icons/loader.svg';
|
||||
import {ReactComponent as CheckmarkIcon} from '../../images/icons/checkmark.svg';
|
||||
import {getCurrencySymbol, getPriceString, getStripeAmount, getMemberActivePrice, getProductFromPrice, getFreeTierTitle, getFreeTierDescription, getFreeProduct, getFreeProductBenefits, formatNumber, isCookiesDisabled, hasOnlyFreeProduct} from '../../utils/helpers';
|
||||
import {getCurrencySymbol, getPriceString, getStripeAmount, getMemberActivePrice, getProductFromPrice, getFreeTierTitle, getFreeTierDescription, getFreeProduct, getFreeProductBenefits, formatNumber, isCookiesDisabled, hasOnlyFreeProduct, isMemberActivePrice} from '../../utils/helpers';
|
||||
import AppContext from '../../AppContext';
|
||||
import calculateDiscount from '../../utils/discount';
|
||||
|
||||
@ -882,7 +882,7 @@ function ProductDescription({product, selectedPrice, activePrice}) {
|
||||
}
|
||||
|
||||
function ChangeProductCard({product, onPlanSelect}) {
|
||||
const {member} = useContext(AppContext);
|
||||
const {member, site} = useContext(AppContext);
|
||||
const {selectedProduct, setSelectedProduct, selectedInterval} = useContext(ProductsContext);
|
||||
const cardClass = selectedProduct === product.id ? 'gh-portal-product-card checked' : 'gh-portal-product-card';
|
||||
const monthlyPrice = product.monthlyPrice;
|
||||
@ -891,7 +891,7 @@ function ChangeProductCard({product, onPlanSelect}) {
|
||||
|
||||
const selectedPrice = selectedInterval === 'month' ? monthlyPrice : yearlyPrice;
|
||||
|
||||
const currentPlan = (selectedPrice.id === memberActivePrice.id);
|
||||
const currentPlan = isMemberActivePrice({member, site, priceId: selectedPrice.id});
|
||||
|
||||
return (
|
||||
<div className={cardClass + (currentPlan ? ' disabled' : '')} key={product.id} onClick={(e) => {
|
||||
|
@ -327,7 +327,9 @@ describe('Signup', () => {
|
||||
email: 'jamie@example.com',
|
||||
name: 'Jamie Larsen',
|
||||
offerId: undefined,
|
||||
plan: singleTierProduct.yearlyPrice.id
|
||||
plan: singleTierProduct.yearlyPrice.id,
|
||||
tierId: singleTierProduct.id,
|
||||
cadence: 'year'
|
||||
});
|
||||
});
|
||||
|
||||
@ -367,7 +369,9 @@ describe('Signup', () => {
|
||||
email: 'jamie@example.com',
|
||||
name: 'Jamie Larsen',
|
||||
offerId: undefined,
|
||||
plan: singleTierProduct.yearlyPrice.id
|
||||
plan: singleTierProduct.yearlyPrice.id,
|
||||
tierId: singleTierProduct.id,
|
||||
cadence: 'year'
|
||||
});
|
||||
const magicLink = await within(popupIframeDocument).findByText(/now check your email/i);
|
||||
expect(magicLink).toBeInTheDocument();
|
||||
@ -408,7 +412,9 @@ describe('Signup', () => {
|
||||
email: 'jamie@example.com',
|
||||
name: '',
|
||||
offerId: undefined,
|
||||
plan: singleTierProduct.monthlyPrice.id
|
||||
plan: singleTierProduct.monthlyPrice.id,
|
||||
tierId: singleTierProduct.id,
|
||||
cadence: 'month'
|
||||
});
|
||||
});
|
||||
|
||||
@ -444,7 +450,9 @@ describe('Signup', () => {
|
||||
email: 'jamie@example.com',
|
||||
name: 'Jamie Larsen',
|
||||
offerId: undefined,
|
||||
plan: singleTierProduct.yearlyPrice.id
|
||||
plan: singleTierProduct.yearlyPrice.id,
|
||||
tierId: singleTierProduct.id,
|
||||
cadence: 'year'
|
||||
});
|
||||
});
|
||||
|
||||
@ -459,6 +467,7 @@ describe('Signup', () => {
|
||||
offer: FixtureOffer
|
||||
});
|
||||
let planId = FixtureSite.singleTier.basic.products.find(p => p.type === 'paid').monthlyPrice.id;
|
||||
let tier = FixtureSite.singleTier.basic.products.find(p => p.type === 'paid');
|
||||
let offerId = FixtureOffer.id;
|
||||
expect(popupFrame).toBeInTheDocument();
|
||||
expect(triggerButtonFrame).toBeInTheDocument();
|
||||
@ -480,7 +489,9 @@ describe('Signup', () => {
|
||||
email: 'jamie@example.com',
|
||||
name: 'Jamie Larsen',
|
||||
offerId,
|
||||
plan: planId
|
||||
plan: planId,
|
||||
tierId: tier.id,
|
||||
cadence: 'month'
|
||||
});
|
||||
|
||||
window.location.hash = '';
|
||||
@ -501,6 +512,7 @@ describe('Signup', () => {
|
||||
offer: FixtureOffer
|
||||
});
|
||||
let planId = FixtureSite.singleTier.basic.products.find(p => p.type === 'paid').monthlyPrice.id;
|
||||
let tier = FixtureSite.singleTier.basic.products.find(p => p.type === 'paid');
|
||||
let offerId = FixtureOffer.id;
|
||||
expect(popupFrame).toBeInTheDocument();
|
||||
expect(triggerButtonFrame).not.toBeInTheDocument();
|
||||
@ -516,7 +528,9 @@ describe('Signup', () => {
|
||||
email: undefined,
|
||||
name: undefined,
|
||||
offerId: offerId,
|
||||
plan: planId
|
||||
plan: planId,
|
||||
tierId: tier.id,
|
||||
cadence: 'month'
|
||||
});
|
||||
|
||||
window.location.hash = '';
|
||||
@ -679,6 +693,7 @@ describe('Signup', () => {
|
||||
offer: FixtureOffer
|
||||
});
|
||||
let planId = FixtureSite.singleTier.basic.products.find(p => p.type === 'paid').monthlyPrice.id;
|
||||
let tier = FixtureSite.singleTier.basic.products.find(p => p.type === 'paid');
|
||||
let offerId = FixtureOffer.id;
|
||||
expect(popupFrame).toBeInTheDocument();
|
||||
expect(triggerButtonFrame).toBeInTheDocument();
|
||||
@ -700,7 +715,9 @@ describe('Signup', () => {
|
||||
email: 'jamie@example.com',
|
||||
name: 'Jamie Larsen',
|
||||
offerId,
|
||||
plan: planId
|
||||
plan: planId,
|
||||
tierId: tier.id,
|
||||
cadence: 'month'
|
||||
});
|
||||
|
||||
window.location.hash = '';
|
||||
@ -720,6 +737,7 @@ describe('Signup', () => {
|
||||
site,
|
||||
offer: FixtureOffer
|
||||
});
|
||||
const singleTier = FixtureSite.singleTier.basic.products.find(p => p.type === 'paid');
|
||||
let planId = FixtureSite.singleTier.basic.products.find(p => p.type === 'paid').monthlyPrice.id;
|
||||
let offerId = FixtureOffer.id;
|
||||
expect(popupFrame).toBeInTheDocument();
|
||||
@ -736,7 +754,9 @@ describe('Signup', () => {
|
||||
email: undefined,
|
||||
name: undefined,
|
||||
offerId: offerId,
|
||||
plan: planId
|
||||
plan: planId,
|
||||
tierId: singleTier.id,
|
||||
cadence: 'month'
|
||||
});
|
||||
|
||||
window.location.hash = '';
|
||||
|
@ -215,7 +215,9 @@ describe('Logged-in free member', () => {
|
||||
checkoutType: 'upgrade'
|
||||
},
|
||||
offerId: undefined,
|
||||
plan: singleTierProduct.monthlyPrice.id
|
||||
plan: singleTierProduct.monthlyPrice.id,
|
||||
tierId: singleTierProduct.id,
|
||||
cadence: 'month'
|
||||
});
|
||||
});
|
||||
|
||||
@ -250,7 +252,9 @@ describe('Logged-in free member', () => {
|
||||
checkoutType: 'upgrade'
|
||||
},
|
||||
offerId: undefined,
|
||||
plan: singleTierProduct.yearlyPrice.id
|
||||
plan: singleTierProduct.yearlyPrice.id,
|
||||
tierId: singleTierProduct.id,
|
||||
cadence: 'year'
|
||||
});
|
||||
});
|
||||
|
||||
@ -266,6 +270,7 @@ describe('Logged-in free member', () => {
|
||||
offer: FixtureOffer
|
||||
});
|
||||
let planId = FixtureSite.singleTier.basic.products.find(p => p.type === 'paid').monthlyPrice.id;
|
||||
let singleTierProduct = FixtureSite.singleTier.basic.products.find(p => p.type === 'paid');
|
||||
let offerId = FixtureOffer.id;
|
||||
expect(popupFrame).toBeInTheDocument();
|
||||
expect(triggerButtonFrame).toBeInTheDocument();
|
||||
@ -285,7 +290,9 @@ describe('Logged-in free member', () => {
|
||||
email: 'jimmie@example.com',
|
||||
name: 'Jimmie Larson',
|
||||
offerId,
|
||||
plan: planId
|
||||
plan: planId,
|
||||
tierId: singleTierProduct.id,
|
||||
cadence: 'month'
|
||||
});
|
||||
|
||||
window.location.hash = '';
|
||||
@ -308,6 +315,7 @@ describe('Logged-in free member', () => {
|
||||
offer: FixtureOffer
|
||||
});
|
||||
let planId = FixtureSite.singleTier.basic.products.find(p => p.type === 'paid').monthlyPrice.id;
|
||||
let singleTierProduct = FixtureSite.singleTier.basic.products.find(p => p.type === 'paid');
|
||||
let offerId = FixtureOffer.id;
|
||||
expect(popupFrame).toBeInTheDocument();
|
||||
expect(triggerButtonFrame).not.toBeInTheDocument();
|
||||
@ -324,7 +332,9 @@ describe('Logged-in free member', () => {
|
||||
checkoutType: 'upgrade'
|
||||
},
|
||||
offerId: offerId,
|
||||
plan: planId
|
||||
plan: planId,
|
||||
tierId: singleTierProduct.id,
|
||||
cadence: 'month'
|
||||
});
|
||||
|
||||
window.location.hash = '';
|
||||
@ -364,7 +374,9 @@ describe('Logged-in free member', () => {
|
||||
checkoutType: 'upgrade'
|
||||
},
|
||||
offerId: undefined,
|
||||
plan: singleTierProduct.yearlyPrice.id
|
||||
plan: singleTierProduct.yearlyPrice.id,
|
||||
tierId: singleTierProduct.id,
|
||||
cadence: 'year'
|
||||
});
|
||||
});
|
||||
|
||||
@ -380,6 +392,7 @@ describe('Logged-in free member', () => {
|
||||
offer: FixtureOffer
|
||||
});
|
||||
let planId = FixtureSite.multipleTiers.basic.products.find(p => p.type === 'paid').monthlyPrice.id;
|
||||
let singleTierProduct = FixtureSite.multipleTiers.basic.products.find(p => p.type === 'paid');
|
||||
let offerId = FixtureOffer.id;
|
||||
expect(popupFrame).toBeInTheDocument();
|
||||
expect(triggerButtonFrame).toBeInTheDocument();
|
||||
@ -399,7 +412,9 @@ describe('Logged-in free member', () => {
|
||||
email: 'jimmie@example.com',
|
||||
name: 'Jimmie Larson',
|
||||
offerId,
|
||||
plan: planId
|
||||
plan: planId,
|
||||
tierId: singleTierProduct.id,
|
||||
cadence: 'month'
|
||||
});
|
||||
|
||||
window.location.hash = '';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {transformApiSiteData} from './helpers';
|
||||
import {transformApiSiteData, transformApiTiersData} from './helpers';
|
||||
|
||||
function getAnalyticsMetadata() {
|
||||
const analyticsTag = document.querySelector('meta[name=ghost-analytics-id]');
|
||||
@ -311,7 +311,7 @@ function setupGhostApi({siteUrl = window.location.origin, apiUrl, apiKey}) {
|
||||
});
|
||||
},
|
||||
|
||||
async checkoutPlan({plan, cancelUrl, successUrl, email: customerEmail, name, offerId, newsletters, metadata = {}} = {}) {
|
||||
async checkoutPlan({plan, tierId, cadence, cancelUrl, successUrl, email: customerEmail, name, offerId, newsletters, metadata = {}} = {}) {
|
||||
const siteUrlObj = new URL(siteUrl);
|
||||
const identity = await api.member.identity();
|
||||
const url = endpointFor({type: 'members', resource: 'create-stripe-checkout-session'});
|
||||
@ -328,18 +328,8 @@ function setupGhostApi({siteUrl = window.location.origin, apiUrl, apiKey}) {
|
||||
fp_tid: (window.FPROM || window.$FPROM)?.data?.tid,
|
||||
...metadata
|
||||
};
|
||||
const analyticsData = getAnalyticsMetadata();
|
||||
if (analyticsData) {
|
||||
metadataObj.ghost_analytics_entry_id = analyticsData.entry_id;
|
||||
metadataObj.ghost_analytics_source_url = analyticsData.source_url;
|
||||
}
|
||||
return makeRequest({
|
||||
url,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
|
||||
const body = {
|
||||
priceId: offerId ? null : plan,
|
||||
offerId,
|
||||
identity: identity,
|
||||
@ -347,7 +337,19 @@ function setupGhostApi({siteUrl = window.location.origin, apiUrl, apiKey}) {
|
||||
successUrl,
|
||||
cancelUrl,
|
||||
customerEmail: customerEmail
|
||||
})
|
||||
};
|
||||
if (tierId && cadence) {
|
||||
delete body.priceId;
|
||||
body.tierId = offerId ? null : tierId;
|
||||
body.cadence = offerId ? null : cadence;
|
||||
}
|
||||
return makeRequest({
|
||||
url,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
}).then(function (res) {
|
||||
if (!res.ok) {
|
||||
throw new Error('Could not create stripe checkout session');
|
||||
@ -413,7 +415,7 @@ function setupGhostApi({siteUrl = window.location.origin, apiUrl, apiKey}) {
|
||||
});
|
||||
},
|
||||
|
||||
async updateSubscription({subscriptionId, planName, planId, smartCancel, cancelAtPeriodEnd, cancellationReason}) {
|
||||
async updateSubscription({subscriptionId, tierId, cadence, planId, smartCancel, cancelAtPeriodEnd, cancellationReason}) {
|
||||
const identity = await api.member.identity();
|
||||
const url = endpointFor({type: 'members', resource: 'subscriptions'}) + subscriptionId + '/';
|
||||
const body = {
|
||||
@ -427,6 +429,13 @@ function setupGhostApi({siteUrl = window.location.origin, apiUrl, apiKey}) {
|
||||
if (body) {
|
||||
body.metadata = analyticsData;
|
||||
}
|
||||
|
||||
if (tierId && cadence) {
|
||||
delete body.priceId;
|
||||
body.tierId = tierId;
|
||||
body.cadence = cadence;
|
||||
}
|
||||
|
||||
return makeRequest({
|
||||
url,
|
||||
method: 'PUT',
|
||||
@ -456,7 +465,7 @@ function setupGhostApi({siteUrl = window.location.origin, apiUrl, apiKey}) {
|
||||
site = {
|
||||
...settings,
|
||||
newsletters,
|
||||
tiers
|
||||
tiers: transformApiTiersData({tiers})
|
||||
};
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
|
@ -110,6 +110,8 @@ export function getPriceFromSubscription({subscription}) {
|
||||
id: subscription.price.price_id,
|
||||
price: subscription.price.amount / 100,
|
||||
name: subscription.price.nickname,
|
||||
tierId: subscription.tier?.id,
|
||||
cadence: subscription.price?.interval === 'month' ? 'month' : 'year',
|
||||
currency: subscription.price.currency.toLowerCase(),
|
||||
currency_symbol: getCurrencySymbol(subscription.price.currency)
|
||||
};
|
||||
@ -122,6 +124,15 @@ export function getMemberActivePrice({member}) {
|
||||
return getPriceFromSubscription({subscription});
|
||||
}
|
||||
|
||||
export function isMemberActivePrice({priceId, site, member}) {
|
||||
const activePrice = getMemberActivePrice({member});
|
||||
const {tierId, cadence} = getProductCadenceFromPrice({site, priceId});
|
||||
if (activePrice?.tierId === tierId && activePrice?.cadence === cadence) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getSubscriptionFromId({member, subscriptionId}) {
|
||||
if (isPaidMember({member})) {
|
||||
const subscriptions = member.subscriptions || [];
|
||||
@ -452,6 +463,24 @@ export function getProductFromPrice({site, priceId}) {
|
||||
});
|
||||
}
|
||||
|
||||
export function getProductCadenceFromPrice({site, priceId}) {
|
||||
if (priceId === 'free') {
|
||||
return getFreeProduct({site});
|
||||
}
|
||||
const products = getAllProductsForSite({site});
|
||||
const tier = products.find((product) => {
|
||||
return (product?.monthlyPrice?.id === priceId) || (product?.yearlyPrice?.id === priceId);
|
||||
});
|
||||
let cadence = 'month';
|
||||
if (tier?.yearlyPrice?.id === priceId) {
|
||||
cadence = 'year';
|
||||
}
|
||||
return {
|
||||
tierId: tier?.id,
|
||||
cadence
|
||||
};
|
||||
}
|
||||
|
||||
export function getAvailablePrices({site, products = null}) {
|
||||
const {
|
||||
portal_plans: portalPlans = [],
|
||||
@ -659,3 +688,61 @@ export const getUpdatedOfferPrice = ({offer, price, useFormatted = false}) => {
|
||||
export const isActiveOffer = ({offer}) => {
|
||||
return offer?.status === 'active';
|
||||
};
|
||||
|
||||
function createMonthlyPrice({tier, priceId}) {
|
||||
if (tier?.monthly_price) {
|
||||
return {
|
||||
id: `price-${priceId}`,
|
||||
active: true,
|
||||
type: 'recurring',
|
||||
nickname: 'Monthly',
|
||||
currency: tier.currency,
|
||||
amount: tier.monthly_price,
|
||||
interval: 'month'
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function createYearlyPrice({tier, priceId}) {
|
||||
if (tier?.yearly_price) {
|
||||
return {
|
||||
id: `price-${priceId}`,
|
||||
active: true,
|
||||
type: 'recurring',
|
||||
nickname: 'Yearly',
|
||||
currency: tier.currency,
|
||||
amount: tier.yearly_price,
|
||||
interval: 'year'
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function createBenefits({tier}) {
|
||||
tier?.benefits?.map((benefit) => {
|
||||
return {
|
||||
name: benefit
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export const transformApiTiersData = ({tiers}) => {
|
||||
let priceId = 0;
|
||||
|
||||
return tiers.map((tier) => {
|
||||
let monthlyPrice = createMonthlyPrice({tier, priceId});
|
||||
priceId += 1;
|
||||
|
||||
let yearlyPrice = createYearlyPrice({tier, priceId});
|
||||
priceId += 1;
|
||||
|
||||
let benefits = createBenefits({tier});
|
||||
return {
|
||||
...tier,
|
||||
benefits: benefits,
|
||||
monthly_price: monthlyPrice,
|
||||
yearly_price: yearlyPrice
|
||||
};
|
||||
});
|
||||
};
|
||||
|
@ -169,6 +169,8 @@ describe('Helpers - ', () => {
|
||||
const value = getPriceFromSubscription({subscription});
|
||||
expect(value).toStrictEqual({
|
||||
...subscription.price,
|
||||
tierId: undefined,
|
||||
cadence: 'year',
|
||||
stripe_price_id: subscription.price.id,
|
||||
id: subscription.price.price_id,
|
||||
price: subscription.price.amount / 100,
|
||||
|
Loading…
Reference in New Issue
Block a user