Skip to content

Commit a1015d3

Browse files
authored
chore(release): publish v0.3.0 (#22)
1 parent 93395a8 commit a1015d3

64 files changed

Lines changed: 10653 additions & 6 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/plugins/commercetools/.codex-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "commercetools",
33
"displayName": "commercetools",
4-
"version": "0.1.0",
4+
"version": "0.3.0",
55
"description": "Build commercetools solutions faster",
66
"author": {
77
"name": "commercetools",

.agents/plugins/commercetools/skills/commercetools-commerce-patterns/SKILL.md

Lines changed: 191 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Approval Rules
2+
3+
**Source:** https://docs.commercetools.com/api/projects/approval-rules
4+
5+
---
6+
7+
## Overview
8+
9+
Approval Rules are predicates defined on a Business Unit that gate Order creation behind a human approval step. When an order being placed by a buyer matches an Approval Rule's predicate, the platform intercepts the `order from cart` action and places the resulting Order in a `Pending` state instead of immediately confirming it.
10+
11+
---
12+
13+
## Core Concepts
14+
15+
### Approval Rules Are Predicates
16+
17+
An Approval Rule contains a `predicate` string written in the [commercetools query predicate syntax](https://docs.commercetools.com/api/predicates/query). The predicate is evaluated against the **Order** at creation time.
18+
19+
Common predicate fields include:
20+
21+
| Field | Example use case |
22+
|---|---|
23+
| `totalPrice.centAmount` | Orders above a spend threshold require approval |
24+
| `lineItems(quantity > X)` | Large quantity orders trigger approval |
25+
| `lineItems(totalPrice.centAmount > X)` | High-value individual line items |
26+
| Custom fields | Business-specific rules (e.g. product category, custom flags) |
27+
28+
Example predicate: `totalPrice.centAmount > 1000000` (orders over €10,000 at centAmount scale).
29+
30+
### Approvers
31+
32+
Each Approval Rule specifies one or more **approver tiers** — sets of associate roles that must approve the order. Tiers are evaluated sequentially:
33+
34+
- All required approvers in a tier must act before the next tier is evaluated.
35+
- An Approval Rule can have multiple tiers to model multi-level approval chains (e.g. line manager → finance director).
36+
37+
### Requesters
38+
39+
Rules can also restrict which associate roles the rule applies to (i.e. which buyers' orders are subject to the rule). This allows different rules for different buyer roles within the same BU.
40+
41+
---
42+
43+
## Approval Flow State Machine
44+
45+
When an Order matches an Approval Rule, it enters the **approval flow** lifecycle:
46+
47+
```
48+
Order created → Pending
49+
50+
┌───────┴───────┐
51+
│ │
52+
Approved Rejected
53+
54+
Order confirmed / fulfilment continues
55+
```
56+
57+
| State | Description |
58+
|---|---|
59+
| `Pending` | Order is awaiting approval from one or more approver tiers. |
60+
| `Approved` | All required approver tiers have approved. Order proceeds normally. |
61+
| `Rejected` | At least one required approver has rejected. Order is declined. |
62+
63+
The state transitions are driven by the `Approve` and `Reject` update actions (applied via Update ApprovalFlow by ID), which must be called via the `asAssociate` API path.
64+
65+
---
66+
67+
## Evaluation at Order Creation
68+
69+
1. Buyer places an order.
70+
2. The platform evaluates all active Approval Rules on the buyer's Business Unit (and inherited from parent BUs).
71+
3. If **any** rule's predicate matches the order, an **Approval Flow** is created and the Order is placed in `Pending` state.
72+
4. If **no** rule matches, the Order is confirmed immediately.
73+
74+
Rules from parent BUs are inherited by child BUs unless overridden.
75+
76+
---
77+
78+
## Key API Resources
79+
80+
| Resource | Endpoint |
81+
|---|---|
82+
| Approval Rules | `GET /as-associate/{associateId}/in-business-unit/key={businessUnitKey}/approval-rules` |
83+
| Approval Flows | `GET /as-associate/{associateId}/in-business-unit/key={businessUnitKey}/approval-flows` |
84+
| Approve an Order | Update ApprovalFlow by ID (`POST /as-associate/{associateId}/in-business-unit/key={buKey}/orders/{orderId}/approval-flows/{flowId}`) with action `Approve` |
85+
| Reject an Order | Update ApprovalFlow by ID (`POST /as-associate/{associateId}/in-business-unit/key={buKey}/orders/{orderId}/approval-flows/{flowId}`) with action `Reject` |
86+
87+
---
88+
89+
## Implementation Notes
90+
91+
- Only associates with an `approveOrder` permission can call approve/reject actions.
92+
- An order can match multiple Approval Rules simultaneously; the platform creates one Approval Flow that merges all required approver tiers.
93+
- Approval Rules are managed by associates with the `updateApprovalRules` permission (typically a BU admin).
94+
- Approval Rule predicates are validated at creation time — an invalid predicate is rejected with a `400` error.
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# As-Associate API
2+
3+
**Source:** https://docs.commercetools.com/api/projects/associate-roles
4+
5+
---
6+
7+
## Overview
8+
9+
The **As-Associate API** is a special API path prefix in commercetools that enforces Business Unit–scoped permission checks on behalf of a buyer (associate). Any operation that a buyer performs within the context of a Business Unit — reading orders, creating carts, placing orders, managing quotes, approving flows — **must** go through this API path. Calling the standard API paths bypasses the BU permission layer and is only appropriate for merchant/admin contexts.
10+
11+
---
12+
13+
## The API Chain Pattern
14+
15+
The TypeScript SDK exposes the As-Associate API through a fluent builder chain:
16+
17+
```typescript
18+
apiRoot
19+
.asAssociate()
20+
.withAssociateIdValue({ associateId: "customer-id-here" })
21+
.inBusinessUnitKeyWithBusinessUnitKeyValue({ businessUnitKey: "bu-key-here" })
22+
.<resource>()
23+
.<action>()
24+
.execute();
25+
```
26+
27+
### Breaking Down the Chain
28+
29+
| Segment | Purpose |
30+
|---|---|
31+
| `.asAssociate()` | Switches the SDK into the as-associate routing context. |
32+
| `.withAssociateIdValue({ associateId })` | Identifies which customer (associate) is performing the action. The platform validates that this customer is an associate of the target BU. |
33+
| `.inBusinessUnitKeyWithBusinessUnitKeyValue({ businessUnitKey })` | Scopes the operation to a specific Business Unit. |
34+
| `.<resource>()` | The resource type (e.g. `.carts()`, `.orders()`, `.quoteRequests()`). |
35+
36+
### Example: Create a Cart as an Associate
37+
38+
```typescript
39+
const cart = await apiRoot
40+
.asAssociate()
41+
.withAssociateIdValue({ associateId: customerId })
42+
.inBusinessUnitKeyWithBusinessUnitKeyValue({ businessUnitKey: "acme-uk" })
43+
.carts()
44+
.post({
45+
body: {
46+
currency: "GBP",
47+
country: "GB",
48+
businessUnit: { key: "acme-uk", typeId: "business-unit" },
49+
store: { key: "acme-uk-store", typeId: "store" },
50+
},
51+
})
52+
.execute();
53+
```
54+
55+
### Example: Place an Order as an Associate
56+
57+
```typescript
58+
const order = await apiRoot
59+
.asAssociate()
60+
.withAssociateIdValue({ associateId: customerId })
61+
.inBusinessUnitKeyWithBusinessUnitKeyValue({ businessUnitKey: "acme-uk" })
62+
.orders()
63+
.post({
64+
body: {
65+
cart: { id: cart.body.id, typeId: "cart" },
66+
version: cart.body.version,
67+
},
68+
})
69+
.execute();
70+
```
71+
72+
---
73+
74+
## Why the As-Associate Path Is Required
75+
76+
### Platform Permission Enforcement
77+
78+
commercetools enforces B2B permissions **at the API layer** using the as-associate path. When a request arrives via this path, the platform:
79+
80+
1. Verifies the `associateId` is an active associate of the named Business Unit.
81+
2. Checks whether that associate holds the required `associateRole` permission for the requested operation (e.g. `createMyCarts`, `createOrders`, `viewOrders`, `updateApprovalFlows`).
82+
3. Rejects the request with an `AssociateMissingPermissionError` (an HTTP 403-class error) if the permission is absent.
83+
84+
Calling `/carts`, `/orders`, etc. directly (without `asAssociate`) uses project-level OAuth scopes only and does not apply associate-role permission logic. This means:
85+
- A buyer could see or modify another BU's data if project scopes are broad.
86+
- Approval rule enforcement is bypassed.
87+
- Audit trail (who did what, in which BU) is lost.
88+
89+
**Always use the as-associate path for buyer-facing operations.**
90+
91+
---
92+
93+
## Operations Covered
94+
95+
The as-associate path supports the following resource types (non-exhaustive):
96+
97+
| Resource | Typical Operations |
98+
|---|---|
99+
| `carts` | Create, read, update, replicate carts scoped to the BU |
100+
| `orders` | Place orders from cart, read orders |
101+
| `orders/quotes` | Create an order from an accepted Quote |
102+
| `quote-requests` | Create and manage QuoteRequests |
103+
| `quotes` | Read, accept, decline Quotes |
104+
| `order-approval-flows` | Approve or reject pending orders |
105+
| `business-units` | Read BU details, update associates (where permitted) |
106+
107+
---
108+
109+
## REST Equivalent
110+
111+
The SDK chain maps to the following REST path structure:
112+
113+
```
114+
POST /as-associate/{associateId}/in-business-unit/key={businessUnitKey}/carts
115+
GET /as-associate/{associateId}/in-business-unit/key={businessUnitKey}/orders
116+
POST /as-associate/{associateId}/in-business-unit/key={businessUnitKey}/orders
117+
POST /as-associate/{associateId}/in-business-unit/key={businessUnitKey}/quote-requests
118+
POST /as-associate/{associateId}/in-business-unit/key={businessUnitKey}/orders/quotes
119+
```
120+
121+
---
122+
123+
## Common Mistakes
124+
125+
| Mistake | Consequence |
126+
|---|---|
127+
| Using `/carts` instead of `asAssociate().*.carts()` | No associate permission check; any authenticated customer could access any BU's carts if scopes allow. |
128+
| Omitting `businessUnitKey` from the cart body | Cart may not be associated with the BU, breaking approval rule evaluation. |
129+
| Using a merchant/admin token for buyer flows | Bypasses all associate-role enforcement; creates audit and security gaps. |
130+
| Calling approval actions via the standard orders path | Not supported — approval transitions are only available through the as-associate path. |
131+
132+
---
133+
134+
## OAuth Scope
135+
136+
The OAuth token used in as-associate requests should carry:
137+
- `manage_my_orders:{projectKey}` or `manage_orders:{projectKey}` (depending on whether you use customer tokens or merchant tokens acting on behalf of a customer).
138+
- For customer-token flows (recommended for buyer-facing apps): use the `customer` OAuth flow so the token is bound to the specific customer, and the platform cross-checks `associateId` against the token's subject.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# B2B Customer Group Pricing
2+
3+
---
4+
5+
## Overview
6+
7+
Customer Groups are the primary mechanism for applying differentiated pricing to B2B buyers in commercetools. They work as one of several **price selection parameters** that the platform evaluates at query time and at line-item addition to select the correct price from a product variant's price list.
8+
9+
---
10+
11+
## Price Selection Parameters
12+
13+
commercetools uses a combination of the following parameters to select a price from the variant's embedded price array:
14+
15+
| Parameter | Notes |
16+
|---|---|
17+
| `currencyCode` | Required. Drives which price entry is eligible. |
18+
| `country` | Optional. Narrows selection to country-specific prices. |
19+
| `customerGroup` | Optional. Narrows selection to group-specific prices. |
20+
| `channel` | Optional. Narrows selection to channel-specific prices (e.g. distribution channel). |
21+
| `priceDate` (`validFrom` / `validTo`) | Optional. Filters prices that are valid at the given point in time. |
22+
23+
These parameters are also known as **price selection criteria**.
24+
25+
---
26+
27+
## How Price Selection Works
28+
29+
### 1. Product Projection Search
30+
31+
If currency and additional price selection parameters are passed in a Product Projection Search request, the platform applies price selection logic and returns the matching price for each product/variant. See the official docs: [Price Selection](https://docs.commercetools.com/api/projects/products#price-selection).
32+
33+
### 2. Cart Line Items
34+
35+
When `addLineItem` is called on a cart, the platform uses the cart's own price selection parameters (`currencyCode`, `country`, `customerGroup`, `channel`) to resolve the price for that line item. For price selection to work correctly:
36+
37+
- The relevant price selection parameters must be set **on the cart** before or at the time line items are added.
38+
- The Customer Group is typically derived from the customer's profile and propagated to the cart automatically, but it can also be set explicitly.
39+
40+
Reference: [Carts API](https://docs.commercetools.com/api/projects/carts)
41+
42+
### 3. Fallback / Default Price
43+
44+
It is strongly recommended to include a **default price** (one without any customer group, channel, or country constraint) within the variant's pricing model. This serves as a fallback when none of the more specific price selection criteria match, preventing scenarios where no price is resolved.
45+
46+
---
47+
48+
## Customer Group Constraints and Limitations
49+
50+
### Deletion Constraint
51+
52+
A Customer Group **cannot be deleted** while it is still referenced by product pricing entries. Attempting to do so returns an HTTP `400` error with the message:
53+
54+
> "Can not delete a source while it is referenced from at least one 'product'."
55+
56+
**Migration workflow** when replacing a Customer Group:
57+
1. Create the new Customer Group.
58+
2. Update all product prices (and any discounts referencing the old group) to reference the new Customer Group.
59+
3. Delete the old Customer Group once no pricing entries reference it.
60+
61+
### Multiple Groups Per Customer
62+
63+
A Customer can hold **multiple Customer Groups** via `customerGroupAssignments` (up to 500). The legacy single `customerGroup` field still exists but is no longer the only option. Price selection resolves the **best qualifying price across all assigned groups**. If a buyer's pricing tier changes (e.g. they move from "Silver" to "Gold"), update the customer's group assignments accordingly.
64+
65+
---
66+
67+
## B2B Recommendations
68+
69+
- **Segment pricing tiers** using Customer Groups (e.g. `tier-bronze`, `tier-silver`, `tier-gold`, `distributor`, `reseller`).
70+
- **Always define a base/default price** to avoid null prices for customers not assigned to any group.
71+
- **Use Channel + Customer Group** together when pricing also varies by distribution channel (e.g. online vs. in-store B2B).
72+
- **Automate group assignment** as part of the customer onboarding or account management workflow so carts always carry the correct Customer Group from the start.

0 commit comments

Comments
 (0)