| name | zuora-dynamic-pricing-build |
| description | Execute a Commerce Catalog dynamic pricing setup — create custom fields, attributes, products, plans, charges, and rate cards on the tenant |
| argument-hint | ["design reference or direct instructions"] |
| allowed-tools | ["Read","Write","Edit","Glob","Grep","Bash","Agent","mcp__zuora-mcp__manage_commerce_products","mcp__zuora-mcp__manage_commerce_plans","mcp__zuora-mcp__manage_commerce_charges","mcp__zuora-mcp__manage_commerce_context_attributes","mcp__zuora-mcp__manage_custom_fields","mcp__zuora-mcp__query_objects","mcp__zuora-mcp__ask_zuora"] |
You are executing a Commerce Catalog dynamic pricing setup directly on the user's Zuora tenant. This skill uses MCP tools to perform operations — it does NOT generate code.
Preflight: Check DynamicPricing is enabled
Before proceeding, call manage_commerce_charges with {"help": true, "operation": "create_charge"}. If the tool is not available (not listed / not found), stop and tell the user:
The DynamicPricing feature is not enabled on this tenant. Enable it at Settings > Billing > Manage Features > Commerce, then retry.
Input
The user's request: $ARGUMENTS
This could be:
- A reference to a design from
/zuora-dynamic-pricing-design
- Direct instructions to create/update catalog entities
- A request to update rate card prices or add new rows/attribute values
If no design exists and the request is complex, recommend running /zuora-dynamic-pricing-design first.
Tool routing
manage_commerce_context_attributes — create/update context attributes
manage_commerce_products — create/update/delete products (can include plans and charges in one call)
manage_commerce_plans — create/update/delete plans
manage_commerce_charges — create/update/delete/query charges, update tier prices. Use help: true for per-operation guidance before calling.
manage_custom_fields — list/add/update custom field definitions on standard Zuora objects
query_objects — look up existing entities, tier IDs, custom field definitions
mcp__zuora-mcp__ask_zuora — only as a fallback for unresolved product-behavior questions after checking charge help first
Workflow
Step 0: Ensure mapped fields exist
Attributes can map to standard fields or custom fields (__c suffix). Standard fields already exist — only custom fields need creation.
To discover available fields on an object, call query_objects with help: "fields" and the target objectType.
Attributes can map to fields on multiple objects:
Account — standard or custom fields
Subscription — standard or custom fields
RatePlan — standard or custom fields
Usage — usage record fields (preferred for usage charges)
Mapping level: If the design doesn't specify the mapping object for an attribute, confirm with the user before proceeding. Choose based on at what granularity the value changes:
account — fixed per customer, shared across all subscriptions
subscription — can differ between subscriptions for the same customer
rate_plan — set per rate plan instance within a subscription
usage — varies per event/record (only available on usage charges)
If custom fields are needed, use manage_custom_fields to add them. First call list_custom_fields with the target objectType to check what exists, then add_custom_field for each missing field:
objectType — e.g., Account, Subscription, RatePlan
fieldName — must end with __c (auto-appended if omitted)
label — display name in Zuora UI
fieldType — string or integer (default: string)
Confirm each custom field was created successfully before proceeding.
Step 1: Setup context attributes
Create or update context attributes that drive dynamic pricing.
List existing:
Tool: manage_commerce_context_attributes
operation: list_attributes
Create new attribute (custom field mapping):
Tool: manage_commerce_context_attributes
operation: create_attribute
contextSchemaId: "location"
attributeJson: {
"slug": "region",
"name": "Region",
"type": "STRING",
"mapping": [{
"slug": "zuora-region",
"sourceSystem": "zuora",
"path": "Account.Region__c",
"validValues": {"stringValues": ["US", "EU", "APAC"]}
}]
}
Create attribute mapped to standard field:
First use query_objects with help: "fields" and objectType: "Account" to confirm the standard field name, then:
Tool: manage_commerce_context_attributes
operation: create_attribute
contextSchemaId: "customer_context"
attributeJson: {
"slug": "currency",
"name": "Currency",
"type": "STRING",
"mapping": [{
"slug": "zuora-currency",
"sourceSystem": "zuora",
"path": "Account.Currency",
"validValues": {"stringValues": ["USD", "EUR", "GBP"]}
}]
}
Add new values to existing attribute:
Tool: manage_commerce_context_attributes
operation: update_attribute_values
contextSchemaId: "location"
attributeSlug: "region"
... (add new valid values)
Step 2: Create product
Two approaches available:
Option A — Product only (then plan and charge separately):
Tool: manage_commerce_products
operation: create_product
productJson: {
"name": "Product Name",
"description": "...",
"startDate": "2026-01-01",
"endDate": "2099-12-31",
"category": "base"
}
Option B — Full hierarchy in one call (product + plans + charges):
Tool: manage_commerce_products
operation: create_product
productJson: {
"name": "Product Name",
"startDate": "2026-01-01",
"endDate": "2099-12-31",
"category": "base",
"plans": [{
"name": "Standard Plan",
"startDate": "2026-01-01",
"endDate": "2099-12-31",
"activeCurrencies": ["USD"],
"charges": [{
"name": "Per-Seat Charge",
"chargeType": "recurring",
"chargeModel": "per_unit",
"unitOfMeasure": "seat",
"billCycle": {
"type": "default_from_customer",
"period": "bill_cycle_period_month",
"periodAlignment": "align_to_charge",
"timing": "in_advance"
},
"triggerEvent": "contract_effective",
"endDateCondition": "subscription_end",
"pricing": {"unitAmounts": {"USD": 10.00}}
}]
}]
}
Save the returned product id and plan id for subsequent operations.
Product grouping: Pricing variations (on-demand vs reserved, US vs EU) should be rate cards on a SINGLE charge — NOT separate products. Create one product, one plan, one charge with multiple rate cards.
Step 3: Create plan (if using Option A)
Tool: manage_commerce_plans
operation: create_plan
planJson: {
"productKey": "<product-id>",
"name": "Plan Name",
"startDate": "2026-01-01",
"endDate": "2099-12-31",
"activeCurrencies": ["USD"]
}
Constraint: Plan startDate must be >= parent product's startDate.
Step 4: Create charge with dynamic pricing
Before creating a charge, call manage_commerce_charges with help: true to get field guidance for the chosen operation.
Required fields for all charges:
charge.name — display name
charge.chargeType — recurring, one_time, or usage
charge.chargeModel — pricing model enum
charge.productRatePlanId — plan ID from step 2/3
charge.billCycle.type — e.g., default_from_customer
charge.billCycle.period — e.g., bill_cycle_period_month
charge.billCycle.periodAlignment — align_to_charge
charge.billCycle.timing — in_advance or in_arrears (OMIT for usage charges)
charge.triggerEvent — contract_effective, service_activation, or customer_acceptance
charge.endDateCondition — subscription_end
Model-specific required fields:
| Model | Additional required |
|---|
| flat_fee | pricing.flatAmounts (map: {"USD": 99.99}) |
| per_unit | pricing.unitAmounts, unitOfMeasure, defaultQuantity |
| tiered | pricing.tiers, pricing.tierMode, unitOfMeasure |
| volume | pricing.tiers, pricing.tierMode, unitOfMeasure |
| tiered_overage | pricing.tiers (ALL need upTo), pricing.unitAmounts, unitOfMeasure |
| overage | pricing.unitAmounts, unitOfMeasure |
| discount_percentage | pricing.discountPercentages, discountOptions |
| discount_fixed_amount | pricing.discountAmounts, discountOptions |
Adding dynamic pricing (attributes + rate cards):
Tool: manage_commerce_charges
operation: create_charge
chargeJson: {
"charge": {
"name": "Dynamic Per-Unit Charge",
"chargeType": "recurring",
"chargeModel": "per_unit",
"productRatePlanId": "<plan-id>",
"billCycle": {
"type": "default_from_customer",
"period": "bill_cycle_period_month",
"periodAlignment": "align_to_charge",
"timing": "in_advance"
},
"triggerEvent": "contract_effective",
"endDateCondition": "subscription_end",
"unitOfMeasure": "seat",
"defaultQuantity": 1,
"pricing": {"unitAmounts": {"USD": 10.00}},
"attributes": [
{
"name": "Region",
"type": "String",
"mapping": {"object": "account", "field": "Region__c"}
}
],
"rateCards": [
{
"attributes": [
{"name": "Region", "operator": "==", "value": {"stringValue": "US"}}
],
"pricing": {"unitAmounts": {"USD": 10.00}}
},
{
"attributes": [
{"name": "Region", "operator": "==", "value": {"stringValue": "EU"}}
],
"pricing": {"unitAmounts": {"USD": 8.00}}
}
]
}
}
Attribute declaration:
name — attribute display name
type — String, Integer, Double (NOT Decimal), Boolean, Date, Datetime
mapping.object — account, subscription, rate_plan, or usage (choose by granularity: account = per-customer, subscription = per-sub, rate_plan = per-plan instance, usage = per-event)
mapping.field — the field name: standard (e.g., Country) or custom (e.g., Region__c)
Default pricing: The pricing field at charge level is the fallback when no rate card matches. Always include it.
Rate card value wrappers (CRITICAL):
- String:
{"stringValue": "us"}
- Integer:
{"intValue": 42}
- Double:
{"numberValue": 9.99}
- Boolean:
{"boolValue": true}
Supported operators: ==, >, >=, <, <=, between, between-inclusive, matches (regex). NOT supported: !=.
- For
between/between-inclusive, value is an array: [lower, upper]
- First matching rate card wins
Step 5: Verify creation
Query charge metadata with pagination:
Rate cards default to 10 rows per page. Always use rateCardPagination to retrieve all rows:
Tool: manage_commerce_charges
operation: query_charge
queryJson: {
"productRatePlanChargeKey": "<charge-id>",
"rateCardPagination": {"page": 1, "pageSize": 50}
}
If the charge has more rows than pageSize, paginate through all pages to get the complete rate card.
Test dynamic pricing evaluation (use plain values, NOT wrapped):
Tool: manage_commerce_charges
operation: query_charge
queryJson: {
"productRatePlanChargeKey": "<charge-id>",
"attributes": [{"name": "Region", "value": "US"}]
}
Report the created entity IDs and confirm pricing evaluates correctly.
Updating existing rate cards
Update prices
- Get tier IDs:
Tool: query_objects
objectType: "ProductRatePlanChargeTier"
filter: ["ProductRatePlanChargeId.EQ:<charge-id>"]
fields: ["Id", "Tier", "Price", "Currency"]
- Update price:
Tool: manage_commerce_charges
operation: update_tier_price
tierJson: {"id": "<tier-id>", "price": 12.50}
Add a new value option to an existing attribute (BCS)
- Call
manage_commerce_context_attributes with update_attribute_values to add the new valid value.
- Then update the charge to add a new rate card row (see below).
Add a new row to the rate card
First query the existing rate cards with pagination to get all rows:
Tool: manage_commerce_charges
operation: query_charge
queryJson: {
"productRatePlanChargeKey": "<charge-id>",
"rateCardPagination": {"page": 1, "pageSize": 50}
}
Then call manage_commerce_charges with update_charge, providing the full updated rateCards[] array including existing rows plus the new one:
Tool: manage_commerce_charges
operation: update_charge
chargeJson: {
"charge": {
"id": "<charge-id>",
"rateCards": [
... existing rows ...,
{
"attributes": [
{"name": "Region", "operator": "==", "value": {"stringValue": "LATAM"}}
],
"pricing": {"unitAmounts": {"USD": 7.00}}
}
]
}
}
Critical constraints
pricing.flatAmounts is a map {"USD": 99.99}, NOT an array
- Tiered: ranges must not overlap; last tier omits
upTo — EXCEPT tiered_overage where ALL tiers require upTo
- Cannot change
productRatePlanId after charge creation
- Tier IDs NOT returned in charge create/query — must use
query_objects on ProductRatePlanChargeTier
billCycle.timing: include for recurring/one_time (in_advance or in_arrears), OMIT for usage
billCycle.periodAlignment: use align_to_charge
endDateCondition: required, typically subscription_end
- For
update_charge, use fieldsToNull array to explicitly clear fields
- Rate card
attributes array in criteria (NOT attribute_values or criteria)
- Rate card values use
catalog.Value wrappers; query_charge evaluation uses plain values
- Mapped fields must exist on the object before attribute creation (standard fields already exist; custom fields must be created first)
- Execute in order: custom fields (if needed) → context attributes → product → plan → charge
- Consolidate pricing variations into rate cards — don't create separate products