Product Provisioning Fan-Out
Phase 5 of the main workflow — multiple product child workflows running concurrently, all joined by Promise.allOf().
Table of Contents
11.3 Product Provisioning Orchestrator
%%{init: {'theme': 'base'}}%%
flowchart TD
classDef startEnd fill:#2d6a4f,stroke:#1b4332,color:#ffffff,font-weight:bold
classDef childWf fill:#5c4a8a,stroke:#c77dff,color:#ffffff
classDef childWfHuman fill:#e76f51,stroke:#bc4749,color:#ffffff,font-weight:bold
classDef gateway fill:#457b9d,stroke:#1d3557,color:#ffffff
classDef more fill:#6c757d,stroke:#495057,color:#ffffff
FanOut(["▶ ProductProvisioningOrchestrator"]):::startEnd
WireCheck{"Wire\nselected?"}:::gateway
WireWF["WireDomesticWorkflow\n⏳ Human task — Operations review\n⏱ Durable activation timer"]:::childWfHuman
ACHCheck{"ACH\nselected?"}:::gateway
ACHWF["ACHOriginationWorkflow\nTreasury Platform entitlement"]:::childWf
ARPCheck{"Positive Pay\nselected?"}:::gateway
ARPWF["PositivePayWorkflow\n⏳ Human task — specialist review"]:::childWfHuman
RTPCheck{"RTP — Instant Payments\nselected?\nEnterprise only"}:::gateway
RTPWF["RTPProvisioningWorkflow\nInstant Payments Network\nregistration via adapter"]:::childWf
MoreDots["⋯ Additional product\nchild workflows\nall run concurrently"]:::more
Join(["■ Promise.allOf() — all complete"]):::startEnd
FanOut --> WireCheck & ACHCheck & ARPCheck & RTPCheck & MoreDots
WireCheck -- "Yes" --> WireWF --> Join
WireCheck -- "No" --> Join
ACHCheck -- "Yes" --> ACHWF --> Join
ACHCheck -- "No" --> Join
ARPCheck -- "Yes" --> ARPWF --> Join
ARPCheck -- "No" --> Join
RTPCheck -- "Yes" --> RTPWF --> Join
RTPCheck -- "No" --> Join
MoreDots --> Join
Node key 🟪 Automated child workflow · 🟧 Child workflow with embedded human task · 🔷 Product selection gate · ⬜ Remaining product workflows
How the Fan-Out Works
The ProductProvisioningOrchestrator evaluates the case’s product selections against the segment’s available products. For each selected product:
- A check confirms the product is present in the case’s product list
- If yes, a child workflow is started asynchronously
- All child workflows run concurrently — they do not wait for each other
Promise.allOf(childPromises)blocks the orchestrator until every started child workflow completes
// Simplified orchestrator logic
List<Promise<Void>> promises = new ArrayList<>();
if (config.availableProductCodes().contains("WIRE_DOMESTIC")
&& case.hasProduct("WIRE_DOMESTIC")) {
promises.add(Async.procedure(
wireDomesticWorkflow::execute, caseId, productSpec
));
}
// ... repeat for each product in the catalog ...
Promise.allOf(promises.toArray(new Promise[0])).get();
// All products provisioned — return to parent workflow
Product Gating by Segment
The orchestrator only starts child workflows for products available to the case’s segment. The WorkflowConfig.availableProductCodes() set controls this — it is computed once at workflow start from the clientSegment flag:
| Segment | Products Available | Workflows That Can Start |
|---|---|---|
| SMALL | 10 products | Up to 10 child workflows |
| MEDIUM | 14 products | Up to 14 child workflows |
| ENTERPRISE | Full catalog | Up to the full product catalog in parallel |
If a case has clientSegment = SMALL and the intake form mistakenly included an Enterprise-only product, the orchestrator’s product gate prevents that child workflow from starting — the segment config is the authoritative source of truth, not the intake form.
See Product Catalog for the full product catalog.