# Use cases
# Overview
This guide builds on the fetchAllowedPlanConfig reference and its basic example by showing how to build the return value for common scenarios. Each example is a complete fetchAllowedPlanConfig function that you can adapt to your own plans, addons, and business logic. The examples are ordered from simple to complex.
All examples must run in a Node.js v6.0 environment.
Multi-decimal quantities
If multi-decimal support (opens new window) is enabled for your site, read the current quantity from subscription_item.quantity_in_decimal (a decimal string) instead of subscription_item.quantity, which is null on these sites. This applies to the examples below that read a subscription item's quantity, such as Lock quantity edits for specific plans and Allow upgrades and block downgrades for a plan and addon.
# Auto-attach an addon and set a quantity range
Attach a specific addon to one plan and apply the same quantity range to every per-unit plan. Use this when a plan should always ship with a bundled addon and you want to cap how many units a customer can select.
- js
Sample output
{
"items": [
{
"plan_id": "starter2-USD-monthly",
"meta_data": {
"quantity_meta": {
"type": "range",
"min": 1,
"max": 100,
"step": 1
}
}
},
{
"plan_id": "professional2-USD-monthly",
"meta_data": {
"quantity_meta": {
"type": "range",
"min": 1,
"max": 100,
"step": 1
}
}
},
{
"plan_id": "standard2-USD-monthly",
"meta_data": {
"quantity_meta": {
"type": "range",
"min": 1,
"max": 100,
"step": 1
}
}
},
{
"plan_id": "starter1-USD-monthly",
"addons": [
{
"id": "additional-users-addon-USD-monthly"
}
],
"meta_data": {
"quantity_meta": {
"type": "range",
"min": 1,
"max": 100,
"step": 1
}
}
},
{
"plan_id": "professional1-USD-monthly",
"meta_data": {
"quantity_meta": {
"type": "range",
"min": 1,
"max": 100,
"step": 1
}
}
},
{
"plan_id": "standard1-USD-monthly",
"meta_data": {
"quantity_meta": {
"type": "range",
"min": 1,
"max": 100,
"step": 1
}
}
}
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# Restrict plan changes to the current billing frequency
Show only the plans whose billing frequency matches the customer's current subscription. This example applies to all plan types and filters plans by period and period unit within the same product family and currency. To restrict addon item prices, add your own logic.
Custom code
let chargebee = require("chargebee");
exports.fetchAllowedPlanConfig = function (
{ subscription, customer, item_prices },
callback,
logger
) {
let productFamilyId;
let actualAddonList = [];
let currentPlan = item_prices.find(({ item_price }) => item_price.item_type == "plan");
item_prices.forEach((el) => {
if (el.item_price.item_type == "plan") {
chargebee.item
.retrieve(el.item_price.item_id)
.request(function (error, result) {
if (error) {
callback(error, null);
} else {
productFamilyId = result.item.item_family_id;
}
});
} else if (el.item_price.item_type == "addon") {
actualAddonList.push({ id: el.item_price.id });
}
});
function filterItemByItemType(list, type) {
return list.filter(function (e) {
return e.item_type == type;
});
}
function filterItemByBillingFrequency(list, currentPlan) {
return list.filter(function (e) {
return e.period == currentPlan.item_price.period && e.period_unit == currentPlan.item_price.period_unit;
});
}
function filterItemByProductFamilyId(list, value) {
return list.filter(function (e) {
return e.item_family_id == value;
});
}
let output = {};
output.items = [];
output.use_existing_addons = true;
function paginatedFetchItem(offset) {
chargebee.item_price
.list({
limit: 100,
offset,
"item_type[in]": "[plan,addon]",
"currency_code[is]": currentPlan.item_price.currency_code
})
.request(function (error, result) {
if (error) {
callback(error, null);
} else {
let itemList = result.list.map((p) => p.item_price);
itemList = filterItemByProductFamilyId(itemList, productFamilyId);
let filterAddon = filterItemByItemType(itemList, "addon"); // return the ones with equal item_type
let filterPlan = filterItemByItemType(itemList, "plan");
filterPlan = filterItemByBillingFrequency(filterPlan, currentPlan);
filterPlan.forEach((p) => {
chargebee.item.retrieve(p.item_id).request(function (error, result) {
if (error) {
logger.debug("Error", error);
} else {
let item = result.item;
let applicableItems = item.applicable_items;
let item_applicability = item.item_applicability;
let resultVal;
let addonCheck;
let objAddons = {};
objAddons.allowed_addons = [];
for (let i in applicableItems) {
if (applicableItems[i].id) {
addonCheck = applicableItems[i];
if (addonCheck !== null || addonCheck !== 0) {
objAddons.allowed_addons.push(addonCheck);
}
}
}
if (item_applicability === "all") {
resultVal = filterAddon; // all addons applicable
} else {
resultVal = filterAddon.filter(function (o1) {
return objAddons.allowed_addons.some(function (o2) {
return o1.item_id === o2.id; // return the ones with equal id
});
});
}
let allowedAddons = [];
let addons = [];
// null check and compare current plan addons with allowed addons
if (resultVal.length !== 0) {
allowedAddons = resultVal.map(({ id }) => ({ id }));
addons = actualAddonList.filter(function (o1) {
return allowedAddons.some(function (o2) {
return o1.id === o2.id; // return the ones with equal id
});
});
}
let entry = {
plan_id: p.id,
allowed_addons: allowedAddons,
addons
};
if (item.status == "active" && item.enabled_for_checkout && item.enabled_in_portal) {
output.items.push(entry);
}
}
});
});
if ("next_offset" in result) {
paginatedFetchItem(result.next_offset);
} else {
callback(null, output);
}
}
});
}
paginatedFetchItem("");
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
Sample output
{
"use_existing_addons": true,
"items": [
{
"plan_id": "starter-USD-monthly",
"allowed_addons": [
{ "id": "priority-support-USD-monthly" }
],
"addons": [
{ "id": "priority-support-USD-monthly" }
]
},
{
"plan_id": "professional-USD-monthly",
"allowed_addons": [
{ "id": "priority-support-USD-monthly" }
],
"addons": []
}
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Lock quantity edits for specific plans
Allow quantity edits for some plans while locking the quantity for others. In this example, quantity edits are blocked for Plan-1 and Plan-2 (the quantity range is fixed to the current quantity) and allowed for Plan-3. This example applies to quantity-based plans.
Custom code
let chargebee = require("chargebee");
exports.fetchAllowedPlanConfig = function (
{ subscription, customer, item_prices },
callback,
logger
) {
let actualPlanId;
let productFamilyId;
let actualAddonList = [];
let addonItemPriceIds = [];
const editQuantityNotAllowedPlanPricePoint = [
"Plan-1",
"Plan-2"
];
const editQuantityAllowedPlanPricePoint = [
"Plan-3"
];
item_prices.forEach((el) => {
if (el.item_price.item_type == "plan") {
chargebee.item
.retrieve(el.item_price.item_id)
.request(function (error, result) {
if (error) {
callback(error, null);
} else {
actualPlanId = el.item_price.id;
productFamilyId = result.item.item_family_id;
}
});
} else if (el.item_price.item_type == "addon") {
actualAddonList.push({ id: el.item_price.id });
addonItemPriceIds.push(el.item_price.id);
}
});
function filterItemByItemType(list, type) {
return list.filter(function (e) {
return e.item_type == type;
});
}
function filterItemByProductFamilyId(list, value) {
return list.filter(function (e) {
return e.item_family_id == value;
});
}
let output = {};
output.items = [];
output.use_existing_addons = true;
function paginatedFetchItem(offset) {
chargebee.item_price
.list({
limit: 100,
offset,
"item_type[in]": "[plan,addon]",
"enabled_in_portal[is]": "True"
})
.request(function (error, result) {
if (error) {
callback(error, null);
} else {
let itemList = result.list.map((p) => p.item_price);
itemList = filterItemByProductFamilyId(itemList, productFamilyId);
let filterAddon = filterItemByItemType(itemList, "addon"); // return the ones with equal item_type
let filterPlan = filterItemByItemType(itemList, "plan");
filterPlan.forEach((p) => {
chargebee.item.retrieve(p.item_id).request(function (error, result) {
if (error) {
logger.debug("Error", error);
} else {
let item = result.item;
let applicableItems = item.applicable_items;
let item_applicability = item.item_applicability;
let resultVal;
let addonCheck;
let objAddons = {};
objAddons.allowed_addons = [];
for (let i in applicableItems) {
if (applicableItems[i].id) {
addonCheck = applicableItems[i];
if (addonCheck !== null || addonCheck !== 0) {
objAddons.allowed_addons.push(addonCheck);
}
}
}
if (item_applicability === "all") {
resultVal = filterAddon; // all addons applicable
} else {
resultVal = filterAddon.filter(function (o1) {
return objAddons.allowed_addons.some(function (o2) {
return o1.item_id === o2.id; // return the ones with equal id
});
});
}
let allowedAddons = [];
let addons = [];
// null check and compare current plan addons with allowed addons
if (resultVal.length !== 0) {
allowedAddons = resultVal.map(({ id }) => ({ id }));
addons = actualAddonList.filter(function (o1) {
return allowedAddons.some(function (o2) {
return o1.id === o2.id; // return the ones with equal id
});
});
}
let entry = {
plan_id: p.id,
allowed_addons: allowedAddons
};
if (p.id === actualPlanId) {
let subscription_item = subscription.subscription_items.find(items => items.item_price_id === p.id);
let min_quantity = 1;
let max_quantity = 10000000;
if (editQuantityNotAllowedPlanPricePoint.includes(actualPlanId)) {
min_quantity = subscription_item.quantity;
max_quantity = subscription_item.quantity;
}
entry["meta_data"] = {
quantity_meta: {
type: "range",
min: min_quantity,
max: max_quantity,
step: 1
}
};
allowedAddons = allowedAddons.map(addon => {
if (addonItemPriceIds.includes(addon.id)) {
let subscription_item = subscription.subscription_items.find(items => items.item_price_id === addon.id);
let min_quantity = 1;
let max_quantity = 10000000;
addon["meta_data"] = {
quantity_meta: {
type: "range",
min: min_quantity,
max: max_quantity,
step: 1
}
};
}
return addon;
});
entry["allowed_addons"] = allowedAddons;
}
if (item.status == "active") {
output.items.push(entry);
}
}
});
});
if ("next_offset" in result) {
paginatedFetchItem(result.next_offset);
} else {
callback(null, output);
}
}
});
}
paginatedFetchItem("");
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
Sample output
{
"use_existing_addons": true,
"items": [
{
"plan_id": "Plan-1",
"allowed_addons": [
{
"id": "sms-credits-USD-monthly",
"meta_data": {
"quantity_meta": {
"type": "range",
"min": 1,
"max": 10000000,
"step": 1
}
}
}
],
"meta_data": {
"quantity_meta": {
"type": "range",
"min": 5,
"max": 5,
"step": 1
}
}
},
{
"plan_id": "Plan-3",
"allowed_addons": [
{ "id": "sms-credits-USD-monthly" }
]
}
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Allow upgrades and block downgrades for a plan and addon
Let customers increase the quantity of a plan and an addon but prevent them from reducing it. In this example, the minimum quantity for Plan-a and Addon-a is set to the current quantity, so customers can only move up. To remove the addon condition, pass an empty list to increaseQuantityAllowedAddonPricePoint. This example applies to quantity-based plans and addons.
Custom code
let chargebee = require("chargebee");
exports.fetchAllowedPlanConfig = function (
{ subscription, customer, item_prices },
callback,
logger
) {
let actualPlanId;
let productFamilyId;
let actualAddonList = [];
let addonItemPriceIds = [];
const increaseQuantityAllowedPlanPricePoint = [
"Plan-a"
];
const increaseQuantityAllowedAddonPricePoint = [
"Addon-a"
];
item_prices.forEach((el) => {
if (el.item_price.item_type == "plan") {
chargebee.item
.retrieve(el.item_price.item_id)
.request(function (error, result) {
if (error) {
callback(error, null);
} else {
actualPlanId = el.item_price.id;
productFamilyId = result.item.item_family_id;
}
});
} else if (el.item_price.item_type == "addon") {
actualAddonList.push({ id: el.item_price.id });
addonItemPriceIds.push(el.item_price.id);
}
});
function filterItemByItemType(list, type) {
return list.filter(function (e) {
return e.item_type == type;
});
}
function filterItemByProductFamilyId(list, value) {
return list.filter(function (e) {
return e.item_family_id == value;
});
}
let output = {};
output.items = [];
output.use_existing_addons = true;
function paginatedFetchItem(offset) {
chargebee.item_price
.list({
limit: 100,
offset,
"item_type[in]": "[plan,addon]",
"enabled_in_portal[is]": "True"
})
.request(function (error, result) {
if (error) {
callback(error, null);
} else {
let itemList = result.list.map((p) => p.item_price);
itemList = filterItemByProductFamilyId(itemList, productFamilyId);
let filterAddon = filterItemByItemType(itemList, "addon"); // return the ones with equal item_type
let filterPlan = filterItemByItemType(itemList, "plan");
filterPlan.forEach((p) => {
chargebee.item.retrieve(p.item_id).request(function (error, result) {
if (error) {
logger.debug("Error", error);
} else {
let item = result.item;
let applicableItems = item.applicable_items;
let item_applicability = item.item_applicability;
let resultVal;
let addonCheck;
let objAddons = {};
objAddons.allowed_addons = [];
for (let i in applicableItems) {
if (applicableItems[i].id) {
addonCheck = applicableItems[i];
if (addonCheck !== null || addonCheck !== 0) {
objAddons.allowed_addons.push(addonCheck);
}
}
}
if (item_applicability === "all") {
resultVal = filterAddon; // all addons applicable
} else {
resultVal = filterAddon.filter(function (o1) {
return objAddons.allowed_addons.some(function (o2) {
return o1.item_id === o2.id; // return the ones with equal id
});
});
}
let allowedAddons = [];
let addons = [];
// null check and compare current plan addons with allowed addons
if (resultVal.length !== 0) {
allowedAddons = resultVal.map(({ id }) => ({ id }));
addons = actualAddonList.filter(function (o1) {
return allowedAddons.some(function (o2) {
return o1.id === o2.id; // return the ones with equal id
});
});
}
let entry = {
plan_id: p.id,
allowed_addons: allowedAddons
};
if (p.id === actualPlanId) {
let subscription_item = subscription.subscription_items.find(items => items.item_price_id === p.id);
let min_quantity = 1;
let max_quantity = 10000000;
if (increaseQuantityAllowedPlanPricePoint.includes(actualPlanId)) {
min_quantity = subscription_item.quantity;
}
entry["meta_data"] = {
quantity_meta: {
type: "range",
min: min_quantity,
max: max_quantity,
step: 1
}
};
allowedAddons = allowedAddons.map(addon => {
if (addonItemPriceIds.includes(addon.id)) {
let subscription_item = subscription.subscription_items.find(items => items.item_price_id === addon.id);
let min_quantity = 1;
let max_quantity = 10000000;
if (increaseQuantityAllowedAddonPricePoint.includes(addon.id)) {
min_quantity = subscription_item.quantity;
}
addon["meta_data"] = {
quantity_meta: {
type: "range",
min: min_quantity,
max: max_quantity,
step: 1
}
};
}
return addon;
});
entry["allowed_addons"] = allowedAddons;
}
if (item.status == "active") {
output.items.push(entry);
}
}
});
});
if ("next_offset" in result) {
paginatedFetchItem(result.next_offset);
} else {
callback(null, output);
}
}
});
}
paginatedFetchItem("");
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
Sample output
{
"use_existing_addons": true,
"items": [
{
"plan_id": "Plan-a",
"allowed_addons": [
{
"id": "Addon-a",
"meta_data": {
"quantity_meta": {
"type": "range",
"min": 3,
"max": 10000000,
"step": 1
}
}
}
],
"meta_data": {
"quantity_meta": {
"type": "range",
"min": 2,
"max": 10000000,
"step": 1
}
}
},
{
"plan_id": "Plan-b",
"allowed_addons": [
{ "id": "Addon-a" }
]
}
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Allow plan upgrades and block downgrades across the catalog
Compare the current plan with every portal-enabled plan and list only the plans whose price is greater than or equal to the current plan. This example normalizes prices to a monthly baseline, so it accounts for different billing frequencies (for example, a discounted annual price versus a monthly price). It also allows the portal-enabled addons and charges for each plan, matching addon frequency to the plan frequency.
Custom code
var chargebee = require("chargebee")
function listAll(resourceFn, params, done, offset, acc) {
acc = acc || []
var p = Object.assign({}, params)
if (offset) p.offset = offset
resourceFn(p).request(function (err, result) {
if (err) return done(err)
var rows = result.list || []
for (var i = 0; i < rows.length; i++) acc.push(rows[i])
if (result.next_offset) {
return listAll(resourceFn, params, done, result.next_offset, acc)
}
done(null, acc)
})
}
exports.fetchAllowedPlanConfig = function (
{ subscription, customer, item_prices },
callback,
logger
) {
try {
// ----------------------------------
// CURRENT PLAN
// ----------------------------------
var currentPlanItem = (subscription.subscription_items || []).find(function (si) {
return si.item_type === "plan"
})
if (!currentPlanItem) return callback(null, { items: [] })
var currentPlanPriceId = currentPlanItem.item_price_id
var currentPlanPayload = (item_prices || []).find(function (r) {
return r.item_price && r.item_price.id === currentPlanPriceId
})
if (!currentPlanPayload) return callback(null, { items: [] })
var currentPlanPrice = currentPlanPayload.item_price
// Correct currency source
var currency = subscription.currency_code
var family = currentPlanPrice.item_family_id
var currentCadence = currentPlanPrice.period_unit
// ----------------------------------
// 1. Fetch portal-enabled plan items (same family)
// ----------------------------------
listAll(
chargebee.item.list,
{
limit: 100,
item_type: { is: "plan" },
enabled_in_portal: { is: true },
item_family_id: { is: family }
},
function (planItemErr, planItems) {
if (planItemErr) return callback(planItemErr)
var planItemIds = planItems.map(function (r) {
return r.item.id
})
// ----------------------------------
// Fetch plan item prices (currency scoped)
// ----------------------------------
listAll(
chargebee.item_price.list,
{
limit: 100,
status: { is: "active" },
currency_code: { is: currency }
},
function (priceErr, priceRows) {
if (priceErr) return callback(priceErr)
var allPrices = priceRows.map(function (r) {
return r.item_price
})
var planPrices = allPrices.filter(function (p) {
return (
p.item_type === "plan" &&
planItemIds.indexOf(p.item_id) !== -1
)
})
// ----------------------------------
// Monthly baseline tier comparison
// ----------------------------------
var monthlyBaseline = {}
planPrices.forEach(function (p) {
if (p.period_unit === "month") {
monthlyBaseline[p.item_id] = parseFloat(p.price_in_decimal)
}
})
var currentBaseline = monthlyBaseline[currentPlanPrice.item_id]
var upgradePlans = planPrices.filter(function (p) {
var candidateBaseline = monthlyBaseline[p.item_id]
if (!candidateBaseline || !currentBaseline) return false
return candidateBaseline >= currentBaseline
})
if (upgradePlans.length === 0) {
upgradePlans = [currentPlanPrice]
}
// ----------------------------------
// 2. Fetch portal-enabled addon items
// ----------------------------------
listAll(
chargebee.item.list,
{
limit: 100,
item_type: { is: "addon" },
enabled_in_portal: { is: true }
},
function (addonItemErr, addonItems) {
if (addonItemErr) return callback(addonItemErr)
var addonItemIds = addonItems.map(function (r) {
return r.item.id
})
// ----------------------------------
// 3. Fetch portal-enabled charge items
// ----------------------------------
listAll(
chargebee.item.list,
{
limit: 100,
item_type: { is: "charge" },
enabled_in_portal: { is: true }
},
function (chargeItemErr, chargeItems) {
if (chargeItemErr) return callback(chargeItemErr)
var chargeItemIds = chargeItems.map(function (r) {
return r.item.id
})
// ----------------------------------
// Filter addon and charge item prices
// ----------------------------------
var addonPrices = allPrices.filter(function (p) {
return (
p.item_type === "addon" &&
addonItemIds.indexOf(p.item_id) !== -1
)
})
var chargePrices = allPrices.filter(function (p) {
return (
p.item_type === "charge" &&
chargeItemIds.indexOf(p.item_id) !== -1
)
})
function allowedAddonsForPlan(plan) {
var allowed = []
// Addons must match cadence
addonPrices.forEach(function (p) {
if (p.period_unit === plan.period_unit) {
allowed.push({ id: p.id })
}
})
// Charges ignore cadence
chargePrices.forEach(function (p) {
allowed.push({ id: p.id })
})
return allowed
}
var itemsOut = upgradePlans.map(function (p) {
return {
plan_id: p.id,
allowed_addons: allowedAddonsForPlan(p)
}
})
callback(null, {
items: itemsOut,
use_existing_addons: true
})
}
)
}
)
}
)
}
)
} catch (e) {
logger.error("fetchAllowedPlanConfig error", e)
callback(e)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
Sample output
{
"use_existing_addons": true,
"items": [
{
"plan_id": "professional-USD-monthly",
"allowed_addons": [
{ "id": "priority-support-USD-monthly" },
{ "id": "onboarding-fee-USD" }
]
},
{
"plan_id": "enterprise-USD-monthly",
"allowed_addons": [
{ "id": "priority-support-USD-monthly" },
{ "id": "onboarding-fee-USD" }
]
}
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Legacy Product Catalog
For Legacy Product Catalog (opens new window), see this guide.