Re-architecting to enable reliability, rapid experimentation, and localization.
Flexible enough to experiment with different prices and UI based on demographic and customer information
Opinionated enough to make complex changes very quickly
High level of reliability and availability
render() {
if (isMarketingStarter) {
return this.renderStarterPurchaseButton();
}
return this.renderProfessionalPurchaseButton();
}MarketingPricingPage.js
if (product === 'salesPro') {
if (billingTerm === 'annual') {
return 200 - (200 * 0.2);
}
return 200;
}PriceSelector.js
{
upgrade: {
products: {
SALES_STARTER: {
annual: {
purchaseOrderId: 123
}
}
}
}
}// CheckoutButton.js
<CheckoutButton productName="service-starter">
// PurchaseOrderSelector.js
const purchaseOrderSelector = (state, productName) => {
const purchaseOrder = {
term:
};
if (productName === 'salesStarter') {
return state.upgrade.products.SALES_STARTER[term].purchaseOrder;
}
}Pass in the one product you want to buy
Selectors go and find the data associated with that key in the response from purchase endpoint
Imperative and configuration over convention
Declaritive and convention-ish over configuration
Imperative and configuration over convention
Sales
Starter
Config.js
Sales
Professional
Config.js
Sales
Page
Config.js
Multiple
Products
Page.js
Sales
Enterprise
Config.js
Pure function
Declaritive and convention-ish over configuration
View Layer
Business logic
View Layer
Business logic
View Layer
Business logic
pricing-pages-
self-service-api
View Layer
Any other HubSpot app
Needs to show pricing information for many products
The queries on this information needs to be synchronous and fast
Query all pricing information and store it on the client
Use a grab bag of selector-like functions to lookup the needed information and query the bulk information
fetchProductInformation
calculatedPriceAdapter, availableCurrenciesAdapter, etc
A fetch to our product catalog endpoint that returned pricing information for all products.
Consumers of the function could then store this however they choose.
Takes the response of fetchProductInformation as the first argument and a purchaseConfiguration as the second.
Returns requested data.
const purchaseConfiguration = {
termId: MONTHLY_NO_DISCOUNT,
merchandiseIds: [PRODUCT_SALES_STARTER],
currencyCode: CURRENCY_USD
}
const calculatedPrice = calculatedPriceAdapter(products, purchaseConfiguration)
// {
// monthly: 100,
// annually: 1200
// }const purchaseConfiguration = {
termId: MONTHLY_NO_DISCOUNT,
merchandiseIds: [PRODUCT_SALES_STARTER],
currencyCode: CURRENCY_USD
}
const calculatedPrice = calculatedPriceAdapter(products, purchaseConfiguration)
// {
// monthly: 100,
// annually: 1200
// }const purchaseConfiguration = {
termId: MONTHLY_NO_DISCOUNT,
merchandiseIds: [PRODUCT_SALES_STARTER],
currencyCode: CURRENCY_USD
}const calculatedPriceSelector (state, purchaseConfig) => {
validatePurchaseConfig(purchaseConfig);
return calculatedPriceAdapter(state.products, purchaseConfig);
}
const PriceDisplay = ({ priceInfo }) => {
return <p>{priceInfo.monthly}</p>
}
const mapStateToProps = (state, ownProps) => {
const { selectedProduct } = ownProps;
const purchaseConfiguration = {
termId: ownProps.termId,
merchandiseId: ownProps.merchandiseId,
currencyCode: state.currencyCode
};
priceInfo: priceInformationSelector(state, purchaseConfiguration)
}
export default connect(mapStateToProps)(PriceDisplay);Unique product characteristics
View Layer
Business Logic
React is composed of components that are pure functions of passed in props
Our view layer and business logic functions are a pure functions of the configuration passed in.