Client Side Pricing Engine
The Client Side Pricing Engine is a powerful client-side calculation tool that mirrors Nue's server-side pricing logic, enabling real-time price calculations without server roundtrips. This ensures consistent pricing across your application while providing immediate feedback to users. The engine is designed to handle both New Order Price Calculations and Change Order Price Calculations with the same accuracy as server-side calculations, reducing latency and dependency on network requests.
Primary Use Cases
New Order Price Calculations
This functionality calculates prices for new products based on the following factors:
- Base product pricing
- Quantity selections
- Term length
- Unit of Measure (e.g., Monthly, Annual, One-Time)
- Product options and configurations
Change Order Price Calculations
This functionality provides immediate pricing feedback when users make changes to existing subscriptions, such as:
- Adjusting quantities
- Changing subscription terms
- Renewing subscriptions
- Co-terming subscriptions
- Cancelling subscriptions
Key Benefits
- Real-Time Calculations: Instantly calculate prices based on product configurations and user actions.
- Server-Side Parity: Ensures the same pricing logic is applied as on the server side, ensuring consistency.
- Offline Capability: Calculate pricing without needing to rely on network connectivity.
- Performance: Offload resource-heavy pricing calculations to the client, improving server performance.
- Consistency: Accurate and uniform pricing across all platforms, ensuring a seamless experience.
Installation
Registry Setup
Add the following to your .npmrc
file:
# Node version control
engine-strict=true
# Registry configuration
registry=https://registry.npmjs.org/
@nue-apps:registry=https://gitlab.com/api/v4/projects/42706932/packages/npm/
always-auth=true
# Authentication (contact Nue team for token)
//gitlab.com/api/v4/projects/42706932/packages/npm/:_authToken=${GITLAB_NPM_AUTH_TOKEN}
Package Installation
npm i @nue-apps/nue-pricing-clientjs
Typescript
TS types for all Nue Self Service-related objects are available in the nue-js library.
npm i @nue-apps/nue-js
// Usage
import type { Subscription, Product, LocalPricingEngineSubscriptionChange } from '@nue-apps/nue-js'
New Order Price Calculations
To calculate prices for new products, you typically need to retrieve product data (GET /catalog/product
). Once the data is retrieved, it can be passed to the NueProductCalculator
to perform the price calculation.
Example Code:
import {
NueProductCalculator,
ProductCalculationObject,
} from '@nue-apps/nue-pricing-clientjs';
// Example product data (usually fetched from an API)
const productResponse = {
"id": "01tEi0000088M9QIAU",
"name": "Revenue Dashboard",
"sku": "REVENUE_DASHBOARD",
"priceModel": "Recurring",
"priceBookEntries": [
{
"id": "01uEi000002fr2dIAA",
"listPrice": 9.9,
"uom": {
"name": "User/Month",
"quantityDimension": "User",
"termDimension": "Month"
},
"currencyIsoCode": "USD"
}
],
"uom": {
"name": "User/Month",
"quantityDimension": "User",
"termDimension": "Month"
}
};
// Assuming product data and price book entries are retrieved from API and prepared
const product = productResponse;
const priceBookEntry = product.priceBookEntries[0];
// Construct the product input object for price calculation
const productInput: ProductCalculationObject = {
id: product.id, // Product ID from API response
priceInfo: {
listPrice: priceBookEntry.listPrice,
priceTags: product.productPriceTags || [],
},
productInfo: {
sku: product.sku,
priceModel: product.priceModel,
name: product.name,
productCategory: product.productCategory,
recordType: product.recordType,
showIncludedProductOptions: product.showIncludedProductOptions,
freeTrialType: product.freeTrialType,
freeTrialUnit: product.freeTrialUnit
},
//New order details
quantity: 1,
startDate: "2024-03-20",
term: 12,
uom: priceBookEntry.uom,
productOptions: product.productOptions || [],
};
// Initialize the pricing calculator
const productCalculator = new NueProductCalculator({
termBasis: "30/360",
});
// Perform the pricing calculation
const result = productCalculator.calculate(productInput);
Example Response:
{
"details": {
"deltaACV": 60,
"deltaARR": 60,
"deltaCMRR": 5,
"deltaTCV": 5,
"discountAmount": 0,
"discountPercentage": 0,
"id": "01tfJ000000LWT9QAO",
"listTotal": 5,
"netSalesPrice": 5,
"salesPrice": 5,
"systemDiscount": 0,
"systemDiscountAmount": 0,
"subtotal": 5,
"totalAmount": 5,
"totalPrice": 5
},
"summary": {
"discountAmount": 0,
"discountPercentage": 0,
"listTotal": 5,
"subtotal": 5,
"systemDiscountAmount": 0,
"systemDiscountPercentage": 0,
"totalAmount": 5,
"totalPrice": 5
}
}
Change Order Price Calculations
For Change Order Pricing, the Client Side Pricing Engine can handle subscription changes like adjusting quantities, modifying terms, renewing subscriptions, and co-terminating subscriptions.
API Request for Pricing Engine Data
To streamline the process of collecting data necessary for change order pricing calculations, we provide an API endpoint that fetches all relevant information in one request for a specific customer. This endpoint consolidates the pricing data from multiple sources, making it easier for the Client Side Pricing Engine to perform accurate price calculations for subscription changes across all customer subscriptions.
Request:
GET https://api.nue.io/ss/orders/pricing-engine-input?customerIds=["customerId"]
Example Response:
{
"status": "SUCCESS",
"data": {
"SUB-00000483": {
"changes": [
{
"changeType": "NewProduct",
"endDate": "2025-01-12",
"lineType": "LineItem",
"listTotal": 148.5,
"netSalesPrice": 9.9,
"quantity": 15,
"startDate": "2024-12-13",
"subtotal": 148.5,
"term": 1,
"totalAmount": 148.5,
"totalPrice": 148.5
},
{
"changeType": "UpdateQuantity",
"endDate": "2025-01-12",
"lineType": "LineItem",
"listTotal": 29.7,
"netSalesPrice": 9.9,
"quantity": 3,
"startDate": "2024-12-13",
"subtotal": 29.7,
"term": 1,
"totalAmount": 29.7,
"totalPrice": 29.7
}
],
"orderProduct": {
"endDate": "2025-01-12",
"listPrice": 9.9,
"listTotal": 178.2,
"priceTags": [
{
"active": true,
"code": "10PERCENTOFFDEMO",
"id": "a0XEi000003GRovMAG",
"lastPublishedById": "005Ei00000GgD7tIAF",
"lastPublishedDate": "2024-12-13",
"name": "10% Discount Term-based",
"priceTagType": "Term",
"priceTiers": [
{
"chargeModel": "PerUnit",
"discountPercentage": 10,
"endUnit": 12,
"id": "a0YEi000002EbJVMA0",
"name": "PT-00000000",
"startUnit": 0,
"tierNumber": 1
}
],
"priceType": "Volume",
"publishStatus": "Published",
"recordType": "DiscountDimension",
"startTime": "2023-03-31T16:01Z",
"uomDimension": "Quarter"
}
],
"product": {
"id": "01tEi0000088M9QIAU",
"name": "Revenue Dashboard",
"priceModel": "Recurring",
"productCategory": "RecurringServices",
"showIncludedProductOptions": false,
"sku": "REVENUE_DASHBOARD"
},
"quantity": 18,
"refId": "25bb7873-7649-4c84-b2bd-e8dda2929e03",
"startDate": "2024-12-13",
"subtotal": 178.2,
"term": 1,
"uom": {
"decimalScale": 0,
"name": "User/Month",
"quantityDimension": "User",
"roundingMode": "Up",
"termDimension": "Month"
}
}
}
},
"warnings": []
}
The response data
is a map keyed by each subscription name.
// TypeScript
// You can also import this type from the @nue-apps/nue-js library
import type { LocalPricingEngineSubscriptionChange } from '@nue-apps/nue-js'
// TS type for each value in the map
type LocalPricingEngineSubscriptionChange = {
changes: LocalPricingEngineChanges;
orderProduct: LocalPricingEngineOrderProduct;
}
// TS type for full `data` map
type SubscriptionPricingEngineMap = Record<string, LocalPricingEngineSubscriptionChange>
Recalculating Subscription Pricing
Example Code:
import type { LocalPricingEngineSubscriptionChange, Subscription, SubscriptionWithProduct } from '@nue-apps/nue-js'
type SubscriptionPricingEngineMap = Record<string, LocalPricingEngineSubscriptionChange>
// Fetch Subscription pricing data (add own error handling)
async function getPricingEngineMap(customerId): SubscriptionPricingEngineMap {
try {
const response = await fetch(`https://api.nue.io/ss/orders/subscriptions/pricing-engine-input?customerIds=["customerId"]`);
const parsed = await response.json();
return parsed?.data
} catch(err) {
// handle error...
}
};
const pricingEngineMap = await getPricingEngineMap();
//The object for the subscription being changed
const subscription: Subscription | SubscriptionWithProduct = subscriptionBeingCalculated;
// Find the matching pricing data from the pricingEngineMap
const pricingData: LocalPricingEngineSubscriptionChange = pricingEngineMap?.[subscription.name];
// Constructing the subscription input object
const subscriptionInput: ChangeOrderCalculationAsset = {
totalPrice: subscription.totalPrice,
totalAmount: subscription.totalAmount,
term: subscription.subscriptionTerm,
assetNumber: subscription.assetNumber || '',
startDate: subscription.subscriptionStartDate,
endDate: subscription.subscriptionEndDate,
quantity: subscription.quantity,
listTotal: pricingData.orderProduct.listTotal,
subtotal: pricingData.orderProduct.subtotal,
...pricingData, // Include rest of the pricing data
};
// Initialize and use the pricing change order calculator
const changeOrderCalculator = new NuePricingChangeOrderCalculator();
//Changes array that includes all changes to the subscription
const changes = [{
changeType: 'Renew',
renewalTerm: 12
}]
const result = changeOrderCalculator.calculateWithAssetChanges(subscriptionInput, changes);
Example Response:
{
"assetPrice": {
"assetNumber": "SUB-000084",
"changeItems": [
{
"deltaACV": 50000,
"deltaARR": 50000,
"deltaCMRR": 4166.666666666667,
"deltaTCV": 600000,
"discountAmount": 0,
"discountPercentage": 0,
"endDate": "2046-03-03",
"listTotal": 600000,
"netSalesPrice": 500,
"quantity": 100,
"salesPrice": 500,
"startDate": "2034-03-04",
"subtotal": 600000,
"systemDiscount": 0,
"systemDiscountAmount": 0,
"term": 12,
"changeType": "Renew",
"totalAmount": 660000,
"totalPrice": 600000
}
],
"endDate": "2046-03-03",
"quantity": 100,
"startDate": "2022-03-04",
"term": 24
},
"priceSummary": {
"totalAmount": 660000,
"totalPrice": 600000
}
}