florist #
A florist on the open network: a Fabric/Beckn v2 provider node that publishes a flower catalogue to the NFH Catalog Service (CATALG) and responds to sales (select → init → confirm) from any consumer node on the network.
Zero runtime dependencies — plain Node.js ≥ 20 (node:crypto provides the Ed25519 signing and BLAKE2b-512 digests the protocol needs).
What's in the shop #
Five flowers, defined in data/catalog.json as Fabric resources with priced offers (plus a provider-level free-delivery offer):
| Item | Price |
|---|---|
ITEM-ROSE-RED-DOZEN — Red Roses, dozen |
€45.00 |
ITEM-TULIP-MIXED-10 — Mixed Tulips, bunch of 10 |
€12.50 |
ITEM-PEONY-PINK-5 — Pink Peonies, bunch of 5 (seasonal offer) |
€24.00 |
ITEM-SUNFLOWER-5 — Sunflowers, bunch of 5 |
€15.00 |
ITEM-LILY-STARGAZER-3 — Stargazer Lilies, bunch of 3 |
€18.50 |
Local stock levels live in data/inventory.json and never leave this node; they gate quotes and get decremented on confirm (and restored on cancel).
Quick start #
# 1. Generate your Ed25519 signing key pair
node bin/florist.js keygen
# 2. Look at the catalogue, check it against CATALG's rules
node bin/florist.js catalog show
node bin/florist.js catalog validate
# 3. Run the provider node (responds to sales, receives publish results)
node bin/florist.js serve
# 4. Publish the catalogue to the network
node bin/florist.js publish
npm test runs the suite, including a full end-to-end signed sales flow against a live server instance.
Joining the network #
Before the live network will accept your publishes, your identity must exist in the DeDi registry (see Onboarding network participants):
florist keygenprints the Base64 raw public key the registry wants.- Create an account on dedi.global, verify your namespace (domain TXT record), create a registry from the Fabric subscriber schema, and publish a subscriber record with your callback base URL (e.g.
https://florist.berjon.com/beckn/) and that public key. Note the record id. - Set
subscriberId(namespace_id/registry_id) andkeyRecordIdinconfig/florist.config.jsonto match, andbppUrito your public HTTPS base URL. CATALG resolves your/catalog/on_publishcallback from the registry entry, not from the request body, so the registered URL must reach the machine runningflorist serve. - Check the registration:
https://fabric.nfh.global/registry/dedi/lookup/<subscriberId>/<recordId>
Every outbound request is signed with a Beckn HTTP Signature: Ed25519 over the (created) (expires) digest signing string, where digest is BLAKE-512={base64-blake2b-512-of-body}.
Tooling #
Publish the catalogue #
florist publish # MERGE — upsert only what changed
florist publish --full # FULL — replace the entire published set
POSTs a signed catalog/publish envelope to the configured Catalog Service. The synchronous ACK means "accepted for processing"; the real per-catalog verdict (ACCEPTED / REJECTED / PARTIAL) arrives asynchronously at /beckn/catalog/on_publish — keep florist serve running to receive it. Results are logged and stored in data/last-publish-result.json.
Add / manage entries #
florist catalog add --name "Ranunculus — Bunch of 8" --price 16.5 \
--botanical "Ranunculus asiaticus" --color coral --stems 8 --stock 20
florist catalog price --id ITEM-RANUNCULUS-BUNCH-OF-8 --price 18
florist catalog remove --id ITEM-RANUNCULUS-BUNCH-OF-8 # then publish --full
florist stock --id ITEM-ROSE-RED-DOZEN --set 36
florist catalog validate
add creates the resource (with RetailResource JSON-LD attributes) and a matching offer with a PriceSpecification consideration in one step. After any change, florist publish pushes it to the network — MERGE mode means you only ship the delta. Removals need publish --full, since MERGE preserves already-published resources.
Respond to sales #
florist serve # default port 4402
florist orders # list received orders
florist orders show --txn <transactionId>
The server implements the Fabric contracting flow on the /beckn/{endpoint} pattern:
| Endpoint | What the florist does |
|---|---|
POST /beckn/select |
Prices the basket against catalogue + stock, ACKs, sends a signed on_select quote |
POST /beckn/init |
Attaches billing/fulfillment, sends on_init with the draft contract and payment terms |
POST /beckn/confirm |
Commits the order, decrements stock, assigns an order id, sends on_confirm |
POST /beckn/status |
Sends on_status with the current contract state |
POST /beckn/cancel |
Cancels, restores stock, sends on_cancel |
POST /beckn/catalog/on_publish |
Records CATALG's publish verdict |
POST /beckn/on_discover |
Receives discovery results for florist discover smoke tests |
Every callback is signed with your key and carries a requestDigest binding it to the originating request. Orders persist as JSON under data/orders/, one file per transactionId, moving through QUOTED → INITIALIZED → CONFIRMED (or CANCELLED).
Check discoverability #
florist discover --text "red roses"
Sends a signed discover intent to the configured Discovery Service; results arrive at /beckn/on_discover and land in data/last-discover-result.json.
Configuration #
config/florist.config.json (each key overridable via FLORIST_* env vars, see src/config.js):
| Key | Meaning |
|---|---|
bppId / bppUri |
Your provider-node identity and public Beckn base URL |
subscriberId / keyRecordId |
DeDi registry identity — these form the signature keyId |
catalogServiceUrl |
CATALG base (default https://fabric.nfh.global/beckn/catalog) |
discoveryServiceUrl |
A DISCOVR instance, for florist discover |
registryUrl |
DeDi registry (default https://fabric.nfh.global/registry/dedi) |
visibleTo |
Network ids to scope distribution to; empty = default global network nfh.global/beckn-nodes |
verifyInboundSignatures |
Verify consumer signatures against the registry — turn on for anything network-facing |
trustedKeys |
"subscriberId|recordId" → public key map for offline/dev verification |
Layout #
bin/florist.js CLI
src/signing.js Beckn HTTP Signature (Ed25519 + BLAKE2b-512 digest)
src/envelope.js Fabric v2 context / ACK / NACK envelopes
src/registry.js DeDi registry lookups (key + callback resolution)
src/client.js Signed outbound POSTs
src/catalog.js Catalogue CRUD + publish payload construction
src/publish.js catalog/publish flow
src/orders.js Contract state machine (quote → init → confirm → cancel)
src/server.js The provider node itself
data/catalog.json The published Fabric Catalog object
data/inventory.json Local-only stock
test/ Signing, catalogue, and end-to-end sales-flow tests
Notes #
- The flower attributes (
botanicalName,stemsPerUnit, …) extend theRetailResource2.1 schema in JSON-LD open-world fashion. If CATALG's domain-schema validation rejects them (on_publish→REJECTEDwithSCH_SCHEMA_VALIDATION_FAILED), trimresourceAttributesdown to the fields the retail schema defines and republish. - Provider location/address in
data/catalog.jsonis a placeholder — set your real coordinates ([longitude, latitude]) before publishing, since spatial discovery indexesprovider.availableAt[*].geo. - Fulfillment-stage actions beyond
status/cancel(update,track) and post-fulfillment (rate,support) are not implemented yet; the server NACKs them withCTX_ACTION_MISMATCH.