diff --git a/.gitignore b/.gitignore index 076e35b..f6bdca9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .DS_Store generic-devkit/postman/.DS_Store generic-devkit/install/ngrok.yml +generic-devkit/data/ diff --git a/generic-devkit/README.md b/generic-devkit/README.md index 80aa8ba..cd3ad7b 100644 --- a/generic-devkit/README.md +++ b/generic-devkit/README.md @@ -10,7 +10,7 @@ select → on_select → init → on_init → confirm → on_confirm → status / on_status → track / on_track → update / on_update → cancel / on_cancel → rate / on_rate → support / on_support -(+ catalog/publish → catalog/on_publish) +(+ catalog/publish -- a DS-internal trigger, not part of the signed transaction flow above) ``` --- @@ -80,7 +80,6 @@ Use the **BAP collection** to drive the transaction lifecycle in order: | 8 | `cancel` | Fulfillment | | 9 | `rate` | Post-Fulfillment | | 10 | `support` | Post-Fulfillment | -| 11 | `catalog/publish` | Catalog Publishing | Each request returns an `ACK`. The corresponding `on_*` callback from the BPP arrives at `sandbox-bap` and can be viewed in the BAP logs: @@ -93,6 +92,58 @@ Use the **BPP collection** to simulate BPP-initiated callbacks directly (e.g. un --- +## Catalog Publisher (`catalog/publish`) + +`onix-bpp` exposes `/catalog/publish` — a DS-internal trigger that publishes one or more plain Beckn Catalog objects: it diffs each against what was last published (producing a fresh baseline, an incremental change file, or a no-op), signs the result, and writes a manifest + catalog index under the handler's `outputRoot` (`/beckn` in the container, `generic-devkit/data/beckn` on the host — see `docker-compose-generic-local.yml`). This is an unsigned, same-operator call, **not** the full signed, async `catalog/publish` Beckn action beckn.yaml describes (context/action envelope, routing, `on_publish` callback) — that is a materially larger scope this devkit does not implement yet. See [beckn-onix's catalogpublisher README](https://github.com/beckn/beckn-onix/blob/catalog-publisher/pkg/plugin/implementation/catalogpublisher/README.md) for the full design background. + +### Trigger it + +Use the **`publish`** request under the BPP collection's **`2 — Catalog Publishing`** folder, or call it directly: + +```bash +curl -X POST http://localhost:8082/catalog/publish \ + -H "Content-Type: application/json" \ + -d '{ + "context": { "action": "catalog/publish" }, + "message": { + "catalogs": [ + { "id": "staging.p-node.fabric.nfh.global/CAT-GENERIC-001", "descriptor": { "name": "Generic Catalog" }, "provider": { "id": "PROV-EXAMPLE-01" }, "resources": [ /* ... */ ] } + ], + "publishDirectives": [ + { "catalogId": "staging.p-node.fabric.nfh.global/CAT-GENERIC-001", "visibleTo": ["beckn.one/testnet", "nfh.global/testnet"], "catalogType": "REGULAR" } + ] + } + }' +``` + +Request body matches beckn.yaml's real `CatalogPublishAction` envelope shape (`context`/`message.catalogs[]`/`message.publishDirectives[]`) — `context` only carries `action` since every other `Context` field is optional and none are meaningful for this unsigned, same-operator call. `publishDirectives[]` entries are matched to a catalog by `catalogId`; `catalogType` (`MASTER`/`REGULAR`) is required by the spec, and `visibleTo` restricts which networks may fetch that catalog (empty/omitted means public) — both map straight onto the same-named fields in the published catalog index. Each catalog's own top-level `"id"` is used verbatim as its catalogId — it is not derived from a domain, so submit the full id you want published. `retire` (a list of catalogIds) and `forceBaseline` (bypass diffing, publish a fresh baseline) are this handler's own additions with no beckn.yaml equivalent — accepted as siblings of `context`/`message`, alongside or instead of `message.catalogs`. + +### Sample response + +```json +{ + "status": "COMPLETED", + "results": [ + { "catalogId": "staging.p-node.fabric.nfh.global/CAT-GENERIC-001", "status": "ACCEPTED", "version": 1 } + ] +} +``` + +`status` is always present (`COMPLETED`/`FAILED` for the call as a whole); each entry in `results` borrows beckn.yaml's `CatalogProcessingResult` vocabulary (`ACCEPTED`/`REJECTED`) per catalog — a bad submission (e.g. missing `id`) is `REJECTED` with a `reason`, without failing the rest of the batch: + +```json +{ + "status": "COMPLETED", + "results": [ + { "catalogId": "", "status": "REJECTED", "reason": "missing catalogId" } + ] +} +``` + +A fatal failure (e.g. signing failure) returns `200` with `status: FAILED` and an `error` object instead. Publishing the same catalogId again with edited `resources`/`offers` produces an incremental change file and bumps its version instead of a fresh baseline; publishing it unchanged is a no-op. Inspect `generic-devkit/data/beckn/` on the host to see the generated manifest, catalog index, and versioned catalog files directly. + +--- + ## Configuration Reference ### Collection Variables (Postman) diff --git a/generic-devkit/config/generic-bpp.yaml b/generic-devkit/config/generic-bpp.yaml index 31447e2..c2e9d3c 100644 --- a/generic-devkit/config/generic-bpp.yaml +++ b/generic-devkit/config/generic-bpp.yaml @@ -44,8 +44,6 @@ modules: keyManager: id: simplekeymanager config: - # Testnet sandbox subscriber registered on beckn.one/testnet - # Replace with your own registered subscriber ID and keys for production subscriberId: bpp.example.com keyId: 76EU7ofwRCF1aobQkShARrf1PAUsNpHqWUJoynPu9w45YFKmzqaPmy signingPrivateKey: 393fVJJsfqssJCRAjedKR2cKAjSLrjU3t+bICQ5KzYM= @@ -160,3 +158,97 @@ modules: - validateSchema - sign - validateAckSign + + # ───────────────────────────────────────────────────────────────── + # catalog/publish — DS-internal trigger for the catalog-publisher + # plugin. Unsigned, same-operator call (not a network-facing Beckn + # action): no validateSign/addRoute/signAck. Given one or more plain + # Beckn Catalog objects, it diffs against what was last published + # (baseline vs. incremental change file), signs the result, and + # writes it under outputRoot. Public catalogs only in this phase. + # Listens at: http://localhost:8082/catalog/publish + # See beckn-onix's pkg/plugin/implementation/catalogpublisher/README.md + # ───────────────────────────────────────────────────────────────── + - name: catalogPublish + path: /catalog/publish + handler: + type: catalogPublish + role: bpp + outputRoot: /beckn + httpClientConfig: + maxIdleConns: 1000 + maxIdleConnsPerHost: 200 + idleConnTimeout: 300s + responseHeaderTimeout: 5s + plugins: + # registry+cache exist only to satisfy keyManager's constructor + # (KeyManagerProvider.New always requires a RegistryLookup); the + # publisher itself never calls either. + registry: + id: dediregistry + config: + timeout: 10 + retry_max: 3 + retry_wait_min: 100ms + retry_wait_max: 500ms + cache: + id: cache + config: + addr: redis:6379 + keyManager: + id: simplekeymanager + config: + subscriberId: staging.p-node.fabric.nfh.global + keyId: 76EU7aSyU6nCGC9i2svAkZUwJvCpBx92LvBrAZR48YVtRg3SjH86vM + signingPrivateKey: psy4wPmeHbD7rkF3JIYL2hbNien62BNCtr6T1C2DoaI= + signingPublicKey: gFoAD0w7SgnB3UaqMr7wK0uq4J0XWmfo4g+WPyhIXgY= + encrPrivateKey: psy4wPmeHbD7rkF3JIYL2hbNien62BNCtr6T1C2DoaI= + encrPublicKey: gFoAD0w7SgnB3UaqMr7wK0uq4J0XWmfo4g+WPyhIXgY= + catalogPublisher: + id: catalogpublisher + config: + # No subscriberId here -- the handler derives it automatically + # from keyManager.config.subscriberId above (the single source + # of truth for which keyset gets loaded). Set it explicitly + # here only if it must legitimately differ from keyManager's. + # indexSchemaURL is likewise not configurable -- it's a + # hardcoded package constant pending a shared becknconstants + # home for canonical schema URLs. + nextUpdateIn: "336h" + fileValidityIn: "336h" + # catalogBaseURL: the one public URL prefix outputRoot is + # served under -- beckn-router's ngrok tunnel, at the /beckn/* + # route added to install/Caddyfile (serves ../data/beckn + # statically). Update this if your ngrok domain changes. + catalogBaseURL: "https://angular-absently-gab.ngrok-free.dev/beckn" + # manifestSubscriberId: the DeDi-native namespace/registry/ + # recordName path for this node's manifest record -- NOT the + # same as keyManager.config.subscriberId above (a plain Beckn + # subscriberId used for keyset lookup/signing; setting *that* + # to a 3-part path would break signing instead). Used only by + # the manifestLoader check below. Replace with your real + # record's namespace/registry/recordName. + manifestSubscriberId: "nfh.global/staging-nodes/staging-p-node" + # Validates submitted catalogs before they're published: the + # handler wraps them in {context:{action:"catalog/publish"}, + # message:{catalogs:[...]}} and validates that envelope against + # catalog/publish's real message.catalogs[]:Catalog[] schema (no + # type/location set -- same as bppTxnReceiver/bppTxnCaller above, + # so it resolves the canonical protocol-specifications-v2 beckn.yaml + # via becknConstants). checkPolicy (opapolicychecker) intentionally + # left out here -- not needed yet. + schemaValidator: + id: schemav2validator + config: + cacheTTL: "3600" + # Read-only: checks (via dediregistry, using catalogPublisher. + # config.manifestSubscriberId above -- same self-lookup + # schemaversionmediator already does at cold start, via its own + # nodeId config) whether the node manifest already declares this + # publisher's catalog index under catalog.catalogIndexes. If not, a + # proposed updated manifest is staged locally under + # outputRoot/index/node-manifest.staged.yaml and a warning is + # returned -- the real manifest on DeDi is never written to by + # this handler; review the staged file and push it yourself. + manifestLoader: + id: manifestloader diff --git a/generic-devkit/install/Caddyfile b/generic-devkit/install/Caddyfile index d9bd46c..088adc2 100644 --- a/generic-devkit/install/Caddyfile +++ b/generic-devkit/install/Caddyfile @@ -5,8 +5,12 @@ # beckn-router — single public entry point for both adapters # Port 9000 is mapped to the host; ngrok tunnels this one port. # -# /bap/* → onix-bap:8081 (BAP receiver and caller) -# /bpp/* → onix-bpp:8082 (BPP receiver and caller) +# /bap/* → onix-bap:8081 (BAP receiver and caller) +# /bpp/* → onix-bpp:8082 (BPP receiver and caller) +# /beckn/* → static files under ../data/beckn (catalogPublish's +# outputRoot) -- the manifest/index/catalog files a crawler fetches. +# Mirrors outputRoot's own layout 1:1, so catalogBaseURL in +# generic-bpp.yaml is this tunnel's URL + "/beckn". :9000 { handle /bap/* { reverse_proxy onix-bap:8081 @@ -14,4 +18,9 @@ handle /bpp/* { reverse_proxy onix-bpp:8082 } + handle /beckn/* { + root * /srv/beckn + uri strip_prefix /beckn + file_server + } } diff --git a/generic-devkit/install/docker-compose-generic-local.yml b/generic-devkit/install/docker-compose-generic-local.yml index f339e0e..8c72eae 100644 --- a/generic-devkit/install/docker-compose-generic-local.yml +++ b/generic-devkit/install/docker-compose-generic-local.yml @@ -1,4 +1,31 @@ services: + # ============================================================ + # beckn-router — Caddy reverse proxy, single entry point for + # both adapters. Exposes port 9000 on the host. + # + # /bap/* → onix-bap:8081 + # /bpp/* → onix-bpp:8082 + # + # For public-internet callbacks, tunnel port 9000 via ngrok: + # cp ngrok.yml.example ngrok.yml # fill in authtoken + domain + # ngrok start --all --config ngrok.yml + # Then set PUBLIC_URL in your Postman collection to the tunnel URL. + # ============================================================ + beckn-router: + image: caddy:alpine + container_name: beckn-router + ports: + - "9000:9000" + networks: + - beckn_network + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile + - ../data/beckn:/srv/beckn:ro + depends_on: + - onix-bap + - onix-bpp + restart: unless-stopped + # ============================================================ # Redis — shared cache for both adapters # ============================================================ @@ -42,6 +69,11 @@ services: # onix-bpp — Beckn adapter acting as BPP # Caller (outbound): http://localhost:8082/bpp/caller/ # Receiver (inbound): http://localhost:8082/bpp/receiver/ + # Catalog publish (internal trigger): http://localhost:8082/catalog/publish + # -- writes manifest/index/catalog files under /beckn in the + # container; ../data/beckn on the host, so they persist across + # `docker compose down`/`up` and can be inspected or moved + # elsewhere. # ============================================================ onix-bpp: image: beckn-onix:latest @@ -59,6 +91,7 @@ services: REDIS_ADDR: redis:6379 volumes: - ../config:/app/config + - ../data/beckn:/beckn command: ["./server", "--config=/app/config/generic-bpp.yaml"] # ============================================================ diff --git a/generic-devkit/install/docker-compose-generic.yml b/generic-devkit/install/docker-compose-generic.yml index 00532c6..e4ce38f 100644 --- a/generic-devkit/install/docker-compose-generic.yml +++ b/generic-devkit/install/docker-compose-generic.yml @@ -20,6 +20,7 @@ services: - beckn_network volumes: - ./Caddyfile:/etc/caddy/Caddyfile + - ../data/beckn:/srv/beckn:ro depends_on: - onix-bap - onix-bpp @@ -47,7 +48,7 @@ services: # Receiver (inbound): http://localhost:8081/bap/receiver/ # ============================================================ onix-bap: - image: fidedocker/onix-adapter + image: fidedocker/onix-adapter:1.7.5 container_name: onix-bap platform: linux/amd64 networks: @@ -68,9 +69,14 @@ services: # onix-bpp — Beckn adapter acting as BPP # Caller (outbound): http://localhost:8082/bpp/caller/ # Receiver (inbound): http://localhost:8082/bpp/receiver/ + # Catalog publish (internal trigger): http://localhost:8082/catalog/publish + # -- writes manifest/index/catalog files under /beckn in the + # container; ../data/beckn on the host, so they persist across + # `docker compose down`/`up` and can be inspected or moved + # elsewhere. # ============================================================ onix-bpp: - image: fidedocker/onix-adapter + image: fidedocker/onix-adapter:1.7.5 container_name: onix-bpp platform: linux/amd64 networks: @@ -85,6 +91,7 @@ services: REDIS_ADDR: redis:6379 volumes: - ../config:/app/config + - ../data/beckn:/beckn command: ["./server", "--config=/app/config/generic-bpp.yaml"] # ============================================================ diff --git a/generic-devkit/postman/BAPBecknStarterKit.postman_collection.json b/generic-devkit/postman/BAPBecknStarterKit.postman_collection.json index c0ddc9f..12506ab 100644 --- a/generic-devkit/postman/BAPBecknStarterKit.postman_collection.json +++ b/generic-devkit/postman/BAPBecknStarterKit.postman_collection.json @@ -1,223 +1,228 @@ { - "info": { - "_postman_id": "9094f707-1fb4-4bf0-8c92-6f53041a1181", - "name": "BAP Beckn Starter Kit", - "description": "Usecase-agnostic BAP requests for all Beckn Protocol v2.0.0 actions. All schema objects carry JSON-LD @context and @type annotations.\n\nSet bap_adapter_url = http://localhost:8081/bap/caller before running.", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "8677993" - }, - "item": [ - { - "name": "1 — Discovery", - "item": [ - { - "name": "discover", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"context\": {\n \"networkId\": \"{{networkId}}\",\n \"action\": \"discover\",\n \"version\": \"2.0.0\",\n \"bapId\": \"{{bap_id}}\",\n \"bapUri\": \"{{bap_uri}}\",\n \"transactionId\": \"{{transaction_id}}\",\n \"messageId\": \"{{$guid}}\",\n \"timestamp\": \"{{$isoTimestamp}}\",\n \"ttl\": \"PT30S\",\n \"schemaContext\":[]\n },\n \"message\": {\n \"intent\": {\n \"filters\": {\n \"type\": \"jsonpath\",\n \"expression\":\"$.catalogs[*].offers[*] ? (@.offerAttributes.tariffModel == 'FLAT_DISCOUNT' && @.offerAttributes.priceSpecification.price < 100)\" \n }\n }\n }\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{bap_adapter_url}}/discover", - "host": [ - "{{bap_adapter_url}}" - ], - "path": [ - "discover" - ] - }, - "description": "BAP selects resources from catalog.\n\nSpec rules (beckn.yaml):\n• Commitment.required = [status, resources, offer]\n• status.descriptor.code ∈ {DRAFT, ACTIVE, CLOSED}\n• resources = array of Resource (each needs @type, id, descriptor, quantity)\n• offer.required = [@type, id, resourceIds]\n• @context omitted on all core beckn.yaml objects" - }, - "response": [] - } - ] - }, - { - "name": "2 — Transaction", - "item": [ - { - "name": "select", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"context\": {\n \"networkId\": \"{{networkId}}\",\n \"action\": \"select\",\n \"version\": \"2.0.0\",\n \"bapId\": \"{{bap_id}}\",\n \"bapUri\": \"{{bap_uri}}\",\n \"bppId\": \"{{bpp_id}}\",\n \"bppUri\": \"{{bpp_uri}}\",\n \"transactionId\": \"{{transaction_id}}\",\n \"messageId\": \"{{$guid}}\",\n \"timestamp\": \"{{$isoTimestamp}}\",\n \"ttl\": \"PT30S\",\n \"schemaContext\":[]\n },\n \"message\": {\n \"contract\": {\n \"id\": \"draft-contract-0001\",\n \"participants\": [\n {\n \"id\": \"participant-buyer-001\",\n \"descriptor\": {\n \"name\": \"John Doe\",\n \"code\": \"buyer\"\n }\n }\n ],\n \"commitments\": [\n {\n \"id\": \"commitment-001\",\n \"descriptor\": {\n \"name\": \"Basic Service Package\",\n \"code\": \"BSP-001\"\n },\n \"status\": {\n \"code\": \"DRAFT\"\n },\n \"resources\": [\n {\n \"id\": \"resource-001\",\n \"descriptor\": {\n \"name\": \"Basic Service Package\",\n \"code\": \"BSP-001\"\n },\n \"quantity\": {\n \"unitQuantity\": 2,\n \"unitCode\": \"UNIT\"\n }\n }\n ],\n \"offer\": {\n \"id\": \"offer-001\",\n \"resourceIds\": [\n \"resource-001\"\n ]\n }\n }\n ],\n \"consideration\": [\n {\n \"id\": \"consideration-001\",\n \"price\": {\n \"currency\": \"INR\",\n \"value\": \"2000.00\"\n },\n \"status\": {\n \"code\": \"DRAFT\"\n },\n \"breakup\": [\n {\n \"title\": \"Base Price\",\n \"price\": {\n \"currency\": \"INR\",\n \"value\": \"1800.00\"\n }\n },\n {\n \"title\": \"Taxes\",\n \"price\": {\n \"currency\": \"INR\",\n \"value\": \"200.00\"\n }\n }\n ]\n }\n ]\n }\n }\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{bap_adapter_url}}/select", - "host": [ - "{{bap_adapter_url}}" - ], - "path": [ - "select" - ] - }, - "description": "BAP selects resources from catalog.\n\nSpec rules (beckn.yaml):\n• Commitment.required = [status, resources, offer]\n• status.descriptor.code ∈ {DRAFT, ACTIVE, CLOSED}\n• resources = array of Resource (each needs @type, id, descriptor, quantity)\n• offer.required = [@type, id, resourceIds]\n• @context omitted on all core beckn.yaml objects" - }, - "response": [] - }, - { - "name": "init", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"context\": {\n \"networkId\": \"{{networkId}}\",\n \"action\": \"init\",\n \"version\": \"2.0.0\",\n \"bapId\": \"{{bap_id}}\",\n \"bapUri\": \"{{bap_uri}}\",\n \"bppId\": \"{{bpp_id}}\",\n \"bppUri\": \"{{bpp_uri}}\",\n \"transactionId\": \"{{transaction_id}}\",\n \"messageId\": \"{{$guid}}\",\n \"timestamp\": \"{{$isoTimestamp}}\",\n \"ttl\": \"PT30S\"\n },\n \"message\": {\n \"contract\": {\n \"commitments\": [\n {\n \"status\": {\n \"descriptor\": {\n \"code\": \"DRAFT\"\n }\n },\n \"resources\": [\n {\n \"id\": \"resource-001\",\n \"descriptor\": {\n \"name\": \"Basic Service Package\",\n \"code\": \"BSP-001\"\n },\n \"quantity\": {\n \"unitQuantity\": 2,\n \"unitCode\": \"UNIT\"\n }\n }\n ],\n \"offer\": {\n \"id\": \"offer-001\",\n \"resourceIds\": [\n \"resource-001\"\n ]\n }\n }\n ],\n \"participants\": [\n {\n \"id\": \"participant-buyer-001\",\n \"descriptor\": {\n \"name\": \"John Doe\",\n \"code\": \"buyer\"\n }\n }\n ],\n \"performance\": [\n {\n \"id\": \"perf-001\"\n }\n ],\n \"settlements\": [\n {\n \"id\": \"settlement-001\",\n \"status\": \"DRAFT\"\n }\n ]\n }\n }\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{bap_adapter_url}}/init", - "host": [ - "{{bap_adapter_url}}" - ], - "path": [ - "init" - ] - }, - "description": "BAP provides buyer details, performance preferences, and payment intent.\n\nSpec rules (beckn.yaml):\n• participants carry buyer contact via participantAttributes (Attributes bag)\n• performance.required = [@context, @type] per spec — @context kept as spec const\n• settlements carry payment method via settlementAttributes (Attributes bag)\n• @context omitted on all core beckn.yaml objects except Performance (spec-required)" - }, - "response": [] - }, - { - "name": "confirm", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"context\": {\n \"networkId\": \"{{networkId}}\",\n \"action\": \"confirm\",\n \"version\": \"2.0.0\",\n \"bapId\": \"{{bap_id}}\",\n \"bapUri\": \"{{bap_uri}}\",\n \"bppId\": \"{{bpp_id}}\",\n \"bppUri\": \"{{bpp_uri}}\",\n \"transactionId\": \"{{transaction_id}}\",\n \"messageId\": \"{{$guid}}\",\n \"timestamp\": \"{{$isoTimestamp}}\",\n \"ttl\": \"PT30S\"\n },\n \"message\": {\n \"contract\": {\n \"id\": \"draft-contract-0001\",\n \"commitments\": [\n {\n \"id\": \"commitment-001\",\n \"status\": {\n \"descriptor\": {\n \"code\": \"DRAFT\"\n }\n },\n \"resources\": [\n {\n \"id\": \"resource-001\",\n \"descriptor\": {\n \"name\": \"Basic Service Package\",\n \"code\": \"BSP-001\"\n },\n \"quantity\": {\n \"unitQuantity\": 2,\n \"unitCode\": \"UNIT\"\n }\n }\n ],\n \"offer\": {\n \"id\": \"offer-001\",\n \"resourceIds\": [\n \"resource-001\"\n ]\n }\n }\n ],\n \"participants\": [\n {\n \"id\": \"participant-buyer-001\",\n \"descriptor\": {\n \"name\": \"John Doe\",\n \"code\": \"buyer\"\n }\n }\n ],\n \"performance\": [\n {\n \"id\": \"perf-001\"\n }\n ],\n \"settlements\": [\n {\n \"id\": \"settlement-001\",\n \"status\": \"COMPLETE\"\n }\n ]\n }\n }\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{bap_adapter_url}}/confirm", - "host": [ - "{{bap_adapter_url}}" - ], - "path": [ - "confirm" - ] - }, - "description": "BAP confirms the contract after payment or binding commitment." - }, - "response": [] - } - ] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "// Beckn Protocol v2.0.0 — BAP Generic Collection", - "// Pre-request: transaction_id can be overridden here for end-to-end flows", - "if (!pm.collectionVariables.get('transaction_id')) {", - " pm.collectionVariables.set('transaction_id', pm.variables.replaceIn('{{$guid}}'));", - "}" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test('Status code is 200', function () {", - " pm.response.to.have.status(200);", - "});", - "pm.test('Response has ack', function () {", - " const json = pm.response.json();", - " pm.expect(json).to.have.property('message');", - " pm.expect(json.message).to.have.property('ack');", - " pm.expect(json.message.ack.status).to.eql('ACK');", - "});" - ] - } - } - ], - "variable": [ - { - "key": "bap_adapter_url", - "value": "http://localhost:8081/bap/caller", - "description": "BAP adapter caller endpoint (outbound)" - }, - { - "key": "bap_id", - "value": "bap.example.com", - "description": "BAP subscriber ID (FQDN)" - }, - { - "key": "public_url", - "value": "http://beckn-router:9000", - "description": "Base URL for bapUri / bppUri callbacks. Default routes within Docker via beckn-router. For external services (discover/publish): change to https://your-domain.ngrok-free.app" - }, - { - "key": "bap_uri", - "value": "{{public_url}}/bap/receiver", - "description": "BAP receiver callback URI — derived from public_url via beckn-router" - }, - { - "key": "bpp_id", - "value": "bpp.example.com", - "description": "BPP subscriber ID (FQDN)" - }, - { - "key": "bpp_uri", - "value": "{{public_url}}/bpp/receiver", - "description": "BPP receiver callback URI — derived from public_url via beckn-router" - }, - { - "key": "networkId", - "value": "beckn.one/testnet", - "description": "Network identifier" - }, - { - "key": "transaction_id", - "value": "", - "description": "Transaction ID — set once in pre-request script, shared across all calls in a flow" - } - ] -} \ No newline at end of file + "info": { + "_postman_id": "9094f707-1fb4-4bf0-8c92-6f53041a1181", + "name": "BAP Beckn Starter Kit", + "description": "Usecase-agnostic BAP requests for all Beckn Protocol v2.0.0 actions. All schema objects carry JSON-LD @context and @type annotations.\n\nSet bap_adapter_url = http://localhost:8081/bap/caller before running.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "8677993" + }, + "item": [ + { + "name": "1 \u2014 Discovery", + "item": [ + { + "name": "discover", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"context\": {\n \"networkId\": \"{{networkId}}\",\n \"action\": \"discover\",\n \"version\": \"2.0.0\",\n \"bapId\": \"{{bap_id}}\",\n \"bapUri\": \"{{bap_uri}}\",\n \"transactionId\": \"{{transaction_id}}\",\n \"messageId\": \"{{$guid}}\",\n \"timestamp\": \"{{$isoTimestamp}}\",\n \"ttl\": \"PT30S\",\n \"schemaContext\":[]\n },\n \"message\": {\n \"intent\": {\n \"filters\": {\n \"type\": \"jsonpath\",\n \"expression\":\"$.catalogs[*].offers[*] ? (@.offerAttributes.tariffModel == 'FLAT_DISCOUNT' && @.offerAttributes.priceSpecification.price < 100)\" \n }\n }\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{bap_adapter_url}}/discover", + "host": [ + "{{bap_adapter_url}}" + ], + "path": [ + "discover" + ] + }, + "description": "BAP selects resources from catalog.\n\nSpec rules (beckn.yaml):\n\u2022 Commitment.required = [status, resources, offer]\n\u2022 status.descriptor.code \u2208 {DRAFT, ACTIVE, CLOSED}\n\u2022 resources = array of Resource (each needs @type, id, descriptor, quantity)\n\u2022 offer.required = [@type, id, resourceIds]\n\u2022 @context omitted on all core beckn.yaml objects" + }, + "response": [] + } + ] + }, + { + "name": "2 \u2014 Transaction", + "item": [ + { + "name": "select", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"context\": {\n \"networkId\": \"{{networkId}}\",\n \"action\": \"select\",\n \"version\": \"2.0.0\",\n \"bapId\": \"{{bap_id}}\",\n \"bapUri\": \"{{bap_uri}}\",\n \"bppId\": \"{{bpp_id}}\",\n \"bppUri\": \"{{bpp_uri}}\",\n \"transactionId\": \"{{transaction_id}}\",\n \"messageId\": \"{{$guid}}\",\n \"timestamp\": \"{{$isoTimestamp}}\",\n \"ttl\": \"PT30S\",\n \"schemaContext\":[]\n },\n \"message\": {\n \"contract\": {\n \"id\": \"draft-contract-0001\",\n \"participants\": [\n {\n \"id\": \"participant-buyer-001\",\n \"descriptor\": {\n \"name\": \"John Doe\",\n \"code\": \"buyer\"\n }\n }\n ],\n \"commitments\": [\n {\n \"id\": \"commitment-001\",\n \"descriptor\": {\n \"name\": \"Basic Service Package\",\n \"code\": \"BSP-001\"\n },\n \"status\": {\n \"code\": \"DRAFT\"\n },\n \"resources\": [\n {\n \"id\": \"resource-001\",\n \"descriptor\": {\n \"name\": \"Basic Service Package\",\n \"code\": \"BSP-001\"\n },\n \"quantity\": {\n \"unitQuantity\": 2,\n \"unitCode\": \"UNIT\"\n }\n }\n ],\n \"offer\": {\n \"id\": \"offer-001\",\n \"resourceIds\": [\n \"resource-001\"\n ]\n }\n }\n ],\n \"consideration\": [\n {\n \"id\": \"consideration-001\",\n \"price\": {\n \"currency\": \"INR\",\n \"value\": \"2000.00\"\n },\n \"status\": {\n \"code\": \"DRAFT\"\n },\n \"breakup\": [\n {\n \"title\": \"Base Price\",\n \"price\": {\n \"currency\": \"INR\",\n \"value\": \"1800.00\"\n }\n },\n {\n \"title\": \"Taxes\",\n \"price\": {\n \"currency\": \"INR\",\n \"value\": \"200.00\"\n }\n }\n ]\n }\n ]\n }\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{bap_adapter_url}}/select", + "host": [ + "{{bap_adapter_url}}" + ], + "path": [ + "select" + ] + }, + "description": "BAP selects resources from catalog.\n\nSpec rules (beckn.yaml):\n\u2022 Commitment.required = [status, resources, offer]\n\u2022 status.descriptor.code \u2208 {DRAFT, ACTIVE, CLOSED}\n\u2022 resources = array of Resource (each needs @type, id, descriptor, quantity)\n\u2022 offer.required = [@type, id, resourceIds]\n\u2022 @context omitted on all core beckn.yaml objects" + }, + "response": [] + }, + { + "name": "init", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"context\": {\n \"networkId\": \"{{networkId}}\",\n \"action\": \"init\",\n \"version\": \"2.0.0\",\n \"bapId\": \"{{bap_id}}\",\n \"bapUri\": \"{{bap_uri}}\",\n \"bppId\": \"{{bpp_id}}\",\n \"bppUri\": \"{{bpp_uri}}\",\n \"transactionId\": \"{{transaction_id}}\",\n \"messageId\": \"{{$guid}}\",\n \"timestamp\": \"{{$isoTimestamp}}\",\n \"ttl\": \"PT30S\"\n },\n \"message\": {\n \"contract\": {\n \"commitments\": [\n {\n \"status\": {\n \"descriptor\": {\n \"code\": \"DRAFT\"\n }\n },\n \"resources\": [\n {\n \"id\": \"resource-001\",\n \"descriptor\": {\n \"name\": \"Basic Service Package\",\n \"code\": \"BSP-001\"\n },\n \"quantity\": {\n \"unitQuantity\": 2,\n \"unitCode\": \"UNIT\"\n }\n }\n ],\n \"offer\": {\n \"id\": \"offer-001\",\n \"resourceIds\": [\n \"resource-001\"\n ]\n }\n }\n ],\n \"participants\": [\n {\n \"id\": \"participant-buyer-001\",\n \"descriptor\": {\n \"name\": \"John Doe\",\n \"code\": \"buyer\"\n }\n }\n ],\n \"performance\": [\n {\n \"id\": \"perf-001\"\n }\n ],\n \"settlements\": [\n {\n \"id\": \"settlement-001\",\n \"status\": \"DRAFT\"\n }\n ]\n }\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{bap_adapter_url}}/init", + "host": [ + "{{bap_adapter_url}}" + ], + "path": [ + "init" + ] + }, + "description": "BAP provides buyer details, performance preferences, and payment intent.\n\nSpec rules (beckn.yaml):\n\u2022 participants carry buyer contact via participantAttributes (Attributes bag)\n\u2022 performance.required = [@context, @type] per spec \u2014 @context kept as spec const\n\u2022 settlements carry payment method via settlementAttributes (Attributes bag)\n\u2022 @context omitted on all core beckn.yaml objects except Performance (spec-required)" + }, + "response": [] + }, + { + "name": "confirm", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"context\": {\n \"networkId\": \"{{networkId}}\",\n \"action\": \"confirm\",\n \"version\": \"2.0.0\",\n \"bapId\": \"{{bap_id}}\",\n \"bapUri\": \"{{bap_uri}}\",\n \"bppId\": \"{{bpp_id}}\",\n \"bppUri\": \"{{bpp_uri}}\",\n \"transactionId\": \"{{transaction_id}}\",\n \"messageId\": \"{{$guid}}\",\n \"timestamp\": \"{{$isoTimestamp}}\",\n \"ttl\": \"PT30S\"\n },\n \"message\": {\n \"contract\": {\n \"id\": \"draft-contract-0001\",\n \"commitments\": [\n {\n \"id\": \"commitment-001\",\n \"status\": {\n \"descriptor\": {\n \"code\": \"DRAFT\"\n }\n },\n \"resources\": [\n {\n \"id\": \"resource-001\",\n \"descriptor\": {\n \"name\": \"Basic Service Package\",\n \"code\": \"BSP-001\"\n },\n \"quantity\": {\n \"unitQuantity\": 2,\n \"unitCode\": \"UNIT\"\n }\n }\n ],\n \"offer\": {\n \"id\": \"offer-001\",\n \"resourceIds\": [\n \"resource-001\"\n ]\n }\n }\n ],\n \"participants\": [\n {\n \"id\": \"participant-buyer-001\",\n \"descriptor\": {\n \"name\": \"John Doe\",\n \"code\": \"buyer\"\n }\n }\n ],\n \"performance\": [\n {\n \"id\": \"perf-001\"\n }\n ],\n \"settlements\": [\n {\n \"id\": \"settlement-001\",\n \"status\": \"COMPLETE\"\n }\n ]\n }\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{bap_adapter_url}}/confirm", + "host": [ + "{{bap_adapter_url}}" + ], + "path": [ + "confirm" + ] + }, + "description": "BAP confirms the contract after payment or binding commitment." + }, + "response": [] + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "// Beckn Protocol v2.0.0 \u2014 BAP Generic Collection", + "// Pre-request: transaction_id can be overridden here for end-to-end flows", + "if (!pm.collectionVariables.get('transaction_id')) {", + " pm.collectionVariables.set('transaction_id', pm.variables.replaceIn('{{$guid}}'));", + "}" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});", + "pm.test('Response has ack', function () {", + " const json = pm.response.json();", + " pm.expect(json).to.have.property('message');", + " pm.expect(json.message).to.have.property('ack');", + " pm.expect(json.message.ack.status).to.eql('ACK');", + "});" + ] + } + } + ], + "variable": [ + { + "key": "bap_adapter_url", + "value": "http://localhost:8081/bap/caller", + "description": "BAP adapter caller endpoint (outbound)" + }, + { + "key": "bap_id", + "value": "bap.example.com", + "description": "BAP subscriber ID (FQDN)" + }, + { + "key": "public_url", + "value": "http://beckn-router:9000", + "description": "Base URL for bapUri / bppUri callbacks. Default routes within Docker via beckn-router. For external services (discover/publish): change to https://your-domain.ngrok-free.app" + }, + { + "key": "bap_uri", + "value": "{{public_url}}/bap/receiver", + "description": "BAP receiver callback URI \u2014 derived from public_url via beckn-router" + }, + { + "key": "bpp_id", + "value": "bpp.example.com", + "description": "BPP subscriber ID (FQDN)" + }, + { + "key": "bpp_uri", + "value": "{{public_url}}/bpp/receiver", + "description": "BPP receiver callback URI \u2014 derived from public_url via beckn-router" + }, + { + "key": "networkId", + "value": "beckn.one/testnet", + "description": "Network identifier" + }, + { + "key": "transaction_id", + "value": "", + "description": "Transaction ID \u2014 set once in pre-request script, shared across all calls in a flow" + }, + { + "key": "bap_base_url", + "value": "http://localhost:8081", + "description": "BAP adapter host:port, no path suffix." + } + ] +} diff --git a/generic-devkit/postman/BPPBecknStarterKit.postman_collection.json b/generic-devkit/postman/BPPBecknStarterKit.postman_collection.json index 828f6ef..bbe31e2 100644 --- a/generic-devkit/postman/BPPBecknStarterKit.postman_collection.json +++ b/generic-devkit/postman/BPPBecknStarterKit.postman_collection.json @@ -1,225 +1,231 @@ { - "info": { - "_postman_id": "71877d77-839a-44ff-942c-d36937d643a1", - "name": "BPP Beckn Starter Kit", - "description": "Usecase-agnostic BPP on_* callbacks for all Beckn Protocol v2.0.0 responses. All schema objects carry JSON-LD @context and @type annotations.\n\nSet bpp_adapter_url = http://localhost:8082/bpp/caller before running.", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "8677993" - }, - "item": [ - { - "name": "1 — Transaction", - "item": [ - { - "name": "on_select", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"context\": {\n \"networkId\": \"beckn.one/testnet\",\n \"action\": \"on_select\",\n \"version\": \"2.0.0\",\n \"bapId\": \"{{bap_id}}\",\n \"bapUri\": \"{{bap_uri}}\",\n \"bppId\": \"{{bpp_id}}\",\n \"bppUri\": \"{{bpp_uri}}\",\n \"transactionId\": \"{{transaction_id}}\",\n \"messageId\": \"{{$guid}}\",\n \"timestamp\": \"{{$isoTimestamp}}\",\n \"ttl\": \"PT30S\"\n },\n \"message\": {\n \"contract\": {\n \"id\": \"draft-contract-0001\",\n \"participants\": [\n {\n \"id\": \"participant-buyer-001\",\n \"descriptor\": {\n \"name\": \"John Doe\",\n \"code\": \"buyer\"\n }\n }\n ],\n \"commitments\": [\n {\n \"id\": \"commitment-001\",\n \"descriptor\": {\n \"name\": \"Basic Service Package\",\n \"code\": \"BSP-001\"\n },\n \"status\": {\n \"code\": \"DRAFT\"\n },\n \"resources\": [\n {\n \"id\": \"resource-001\",\n \"descriptor\": {\n \"name\": \"Basic Service Package\",\n \"code\": \"BSP-001\"\n },\n \"quantity\": {\n \"unitQuantity\": 2,\n \"unitCode\": \"UNIT\"\n }\n }\n ],\n \"offer\": {\n \"id\": \"offer-001\",\n \"resourceIds\": [\n \"resource-001\"\n ]\n }\n }\n ],\n \"consideration\": [\n {\n \"id\": \"consideration-001\",\n \"price\": {\n \"currency\": \"INR\",\n \"value\": \"2000.00\"\n },\n \"status\": {\n \"code\": \"DRAFT\"\n },\n \"breakup\": [\n {\n \"title\": \"Base Price\",\n \"price\": {\n \"currency\": \"INR\",\n \"value\": \"1800.00\"\n }\n },\n {\n \"title\": \"Taxes\",\n \"price\": {\n \"currency\": \"INR\",\n \"value\": \"200.00\"\n }\n }\n ]\n }\n ]\n }\n }\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{bpp_adapter_url}}/on_select", - "host": [ - "{{bpp_adapter_url}}" - ], - "path": [ - "on_select" - ] - }, - "description": "BPP returns updated contract draft with pricing after selection." - }, - "response": [] - }, - { - "name": "on_init", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"context\": {\n \"networkId\": \"beckn.one/testnet\",\n \"action\": \"on_init\",\n \"version\": \"2.0.0\",\n \"bapId\": \"{{bap_id}}\",\n \"bapUri\": \"{{bap_uri}}\",\n \"bppId\": \"{{bpp_id}}\",\n \"bppUri\": \"{{bpp_uri}}\",\n \"transactionId\": \"{{transaction_id}}\",\n \"messageId\": \"{{$guid}}\",\n \"timestamp\": \"{{$isoTimestamp}}\",\n \"ttl\": \"PT30S\"\n },\n \"message\": {\n \"contract\": {\n \"commitments\": [\n {\n \"status\": {\n \"descriptor\": {\n \"code\": \"DRAFT\"\n }\n },\n \"resources\": [\n {\n \"id\": \"resource-001\",\n \"descriptor\": {\n \"name\": \"Basic Service Package\",\n \"code\": \"BSP-001\"\n },\n \"quantity\": {\n \"unitQuantity\": 2,\n \"unitCode\": \"UNIT\"\n }\n }\n ],\n \"offer\": {\n \"id\": \"offer-001\",\n \"resourceIds\": [\n \"resource-001\"\n ]\n }\n }\n ],\n \"participants\": [\n {\n \"id\": \"participant-buyer-001\",\n \"descriptor\": {\n \"name\": \"John Doe\",\n \"code\": \"buyer\"\n }\n }\n ],\n \"performance\": [\n {\n \"id\": \"perf-001\"\n }\n ],\n \"settlements\": [\n {\n \"id\": \"settlement-001\",\n \"status\": \"DRAFT\"\n }\n ]\n }\n }\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{bpp_adapter_url}}/on_init", - "host": [ - "{{bpp_adapter_url}}" - ], - "path": [ - "on_init" - ] - }, - "description": "BPP confirms initialised contract with payment details." - }, - "response": [] - }, - { - "name": "on_confirm", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"context\": {\n \"networkId\": \"beckn.one/testnet\",\n \"action\": \"on_confirm\",\n \"version\": \"2.0.0\",\n \"bapId\": \"{{bap_id}}\",\n \"bapUri\": \"{{bap_uri}}\",\n \"bppId\": \"{{bpp_id}}\",\n \"bppUri\": \"{{bpp_uri}}\",\n \"transactionId\": \"{{transaction_id}}\",\n \"messageId\": \"{{$guid}}\",\n \"timestamp\": \"{{$isoTimestamp}}\",\n \"ttl\": \"PT30S\"\n },\n \"message\": {\n \"contract\": {\n \"id\": \"draft-contract-0001\",\n \"commitments\": [\n {\n \"id\": \"commitment-001\",\n \"status\": {\n \"descriptor\": {\n \"code\": \"DRAFT\"\n }\n },\n \"resources\": [\n {\n \"id\": \"resource-001\",\n \"descriptor\": {\n \"name\": \"Basic Service Package\",\n \"code\": \"BSP-001\"\n },\n \"quantity\": {\n \"unitQuantity\": 2,\n \"unitCode\": \"UNIT\"\n }\n }\n ],\n \"offer\": {\n \"id\": \"offer-001\",\n \"resourceIds\": [\n \"resource-001\"\n ]\n }\n }\n ],\n \"participants\": [\n {\n \"id\": \"participant-buyer-001\",\n \"descriptor\": {\n \"name\": \"John Doe\",\n \"code\": \"buyer\"\n }\n }\n ],\n \"performance\": [\n {\n \"id\": \"perf-001\"\n }\n ],\n \"settlements\": [\n {\n \"id\": \"settlement-001\",\n \"status\": \"COMPLETE\"\n }\n ]\n }\n }\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{bpp_adapter_url}}/on_confirm", - "host": [ - "{{bpp_adapter_url}}" - ], - "path": [ - "on_confirm" - ] - }, - "description": "BPP confirms contract as binding and provides performance details." - }, - "response": [] - } - ] - }, - { - "name": "2 — Catalog Publishing", - "item": [ - { - "name": "publish", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"context\": {\n \"networkId\": \"beckn.one/testnet\",\n \"action\": \"catalog/publish\",\n \"version\": \"2.0.0\",\n \"bapId\": \"{{bap_id}}\",\n \"bapUri\": \"{{bap_uri}}\",\n \"bppId\": \"{{bpp_id}}\",\n \"bppUri\": \"{{bpp_uri}}\",\n \"transactionId\": \"{{transaction_id}}\",\n \"messageId\": \"{{$guid}}\",\n \"timestamp\": \"{{$isoTimestamp}}\",\n \"ttl\": \"PT30S\"\n },\n \"message\": {\n \"catalogs\": [\n {\n \"id\": \"CAT-GENERIC-001\",\n \"descriptor\": {\n \"name\": \"Generic Catalog\",\n \"shortDesc\": \"Daily essentials generic items\"\n },\n \"provider\": {\n \"id\": \"PROV-EXAMPLE-01\",\n \"descriptor\": {\n \"name\": \"BP Pvt Ltd\"\n },\n \"availableAt\": [\n {\n \"geo\": {\n \"type\": \"Point\",\n \"coordinates\": [\n 77.6401,\n 12.9116\n ]\n },\n \"address\": {\n \"streetAddress\": \"27th Main Rd, Sector 2, HSR Layout\",\n \"addressLocality\": \"Bengaluru\",\n \"addressRegion\": \"Karnataka\",\n \"postalCode\": \"560102\",\n \"addressCountry\": \"IN\"\n }\n }\n ]\n },\n \"resources\": [\n {\n \"id\": \"ITEM-GENERIC-001\",\n \"descriptor\": {\n \"name\": \"Bru Gold Instant Coffee Powder\",\n \"shortDesc\": \"Rich and aromatic instant coffee powder, 200g\",\n \"longDesc\": \"Bru Gold is a premium blend of coffee and chicory, crafted for those who love a rich, flavourful cup. Made from handpicked coffee beans roasted to perfection, this instant coffee powder dissolves quickly to give you a smooth, aromatic brew every time. Whether you prefer your coffee black or with milk, Bru Gold delivers a satisfying coffee experience. Perfect for filter coffee, cappuccino, or a classic South Indian style coffee.\",\n \"mediaFile\": [\n {\n \"uri\": \"https://www.jiomart.com/images/product/original/599173064/599173064.jpg\",\n \"mimeType\": \"image/jpeg\",\n \"label\": \"Product image\"\n }\n ]\n },\n \"provider\": {\n \"id\": \"PROV-EXAMPLE-01\",\n \"descriptor\": {\n \"name\": \"BP Pvt Ltd\"\n }\n },\n \"rating\": {\n \"ratingValue\": 4.1,\n \"ratingCount\": 18200,\n \"bestRating\": 5,\n \"worstRating\": 1\n },\n \"availableAt\": [\n {\n \"geo\": {\n \"type\": \"Point\",\n \"coordinates\": [\n 77.6401,\n 12.9116\n ]\n },\n \"address\": {\n \"streetAddress\": \"27th Main Rd, Sector 2, HSR Layout\",\n \"addressLocality\": \"Bengaluru\",\n \"addressRegion\": \"Karnataka\",\n \"postalCode\": \"560102\",\n \"addressCountry\": \"IN\"\n }\n }\n ]\n },\n {\n \"id\": \"ITEM-GENERIC-002\",\n \"descriptor\": {\n \"name\": \"Generic Classic Item\",\n \"shortDesc\": \"Pure soluble coffee, bold and rich, 100g\",\n \"longDesc\": \"Nescafe Classic is made from 100% pure coffee beans, carefully selected and roasted to deliver a bold, rich flavour in every cup. This instant coffee dissolves instantly in hot or cold water, making it ideal for a quick coffee fix anytime. The deep roast brings out natural coffee aromas that energise your mornings. Loved by coffee enthusiasts across India, Nescafe Classic is the trusted choice for a consistently great cup of coffee.\",\n \"mediaFile\": [\n {\n \"uri\": \"https://m.media-amazon.com/images/S/aplus-media/vc/bb6a0196-cad0-4395-b85e-134ef725c0f7._CR0,0,1251,1251_PT0_SX300__.png\",\n \"mimeType\": \"image/png\",\n \"label\": \"Product image\"\n }\n ]\n },\n \"provider\": {\n \"id\": \"PROV-EXAMPLE-02\",\n \"descriptor\": {\n \"name\": \"Mart Pvt Ltd\"\n }\n },\n \"rating\": {\n \"ratingValue\": 4.1,\n \"ratingCount\": 32500,\n \"bestRating\": 5,\n \"worstRating\": 1\n },\n \"availableAt\": [\n {\n \"geo\": {\n \"type\": \"Point\",\n \"coordinates\": [\n 76.6394,\n 12.2958\n ]\n },\n \"address\": {\n \"streetAddress\": \"12 Sayyaji Rao Road, Devaraja Market\",\n \"addressLocality\": \"Mysore\",\n \"addressRegion\": \"Karnataka\",\n \"postalCode\": \"570001\",\n \"addressCountry\": \"IN\"\n }\n }\n ]\n }\n ],\n \"offers\": [\n {\n \"id\": \"OFFER-GENERIC-002\",\n \"descriptor\": {\n \"name\": \"Generic Bundle\",\n \"shortDesc\": \"Bru Gold + Nescafe Classic at 15% off - your morning coffee essentials\"\n },\n \"resourceIds\": [\n \"ITEM-GENERIC-001\"\n ],\n \"provider\": {\n \"id\": \"PROV-EXAMPLE-01\",\n \"descriptor\": {\n \"name\": \"Mart Pvt Ltd\"\n }\n },\n \"validity\": {\n \"startDate\": \"2026-03-04T00:00:00Z\",\n \"endDate\": \"2026-03-31T23:59:59Z\"\n }\n },\n {\n \"id\": \"OFFER-GROCERY-STAPLES-SAVER-002\",\n \"descriptor\": {\n \"name\": \"Hot Beverages Pack\",\n \"shortDesc\": \"Nescafe Classic + Tata Tea Premium combo - 10% off on your daily beverages\"\n },\n \"resourceIds\": [\n \"ITEM-GENERIC-002\"\n ],\n \"provider\": {\n \"id\": \"PROV-EXAMPLE-01\",\n \"descriptor\": {\n \"name\": \"Mart Pvt Ltd\"\n }\n },\n \"validity\": {\n \"startDate\": \"2026-03-04T00:00:00Z\",\n \"endDate\": \"2026-03-31T23:59:59Z\"\n }\n }\n ],\n \"publishDirectives\": {\n \"catalogType\": \"regular\"\n }\n }\n ]\n }\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{bpp_adapter_url}}/publish", - "host": [ - "{{bpp_adapter_url}}" - ], - "path": [ - "publish" - ] - }, - "description": "Network returns catalog processing result after publish." - }, - "response": [] - } - ] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "requests": {}, - "exec": [ - "// Beckn Protocol v2.0.0 — BPP Generic Collection", - "// Pre-request: set transaction_id to match the BAP collection for end-to-end testing", - "if (!pm.collectionVariables.get('transaction_id')) {", - " pm.collectionVariables.set('transaction_id', pm.variables.replaceIn('{{$guid}}'));", - "}" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "requests": {}, - "exec": [ - "pm.test('Status code is 200', function () {", - " pm.response.to.have.status(200);", - "});", - "pm.test('Response has ack', function () {", - " const json = pm.response.json();", - " pm.expect(json).to.have.property('message');", - " pm.expect(json.message).to.have.property('ack');", - " pm.expect(json.message.ack.status).to.eql('ACK');", - "});" - ] - } - } - ], - "variable": [ - { - "key": "bpp_adapter_url", - "value": "http://localhost:8082/bpp/caller", - "description": "BAP adapter caller endpoint (outbound)" - }, - { - "key": "bap_id", - "value": "bap.example.com", - "description": "BAP subscriber ID (FQDN)" - }, - { - "key": "public_url", - "value": "http://beckn-router:9000", - "description": "Base URL for bapUri / bppUri callbacks. Default routes within Docker via beckn-router. For external services (discover/publish): change to https://your-domain.ngrok-free.app" - }, - { - "key": "bap_uri", - "value": "{{public_url}}/bap/receiver", - "description": "BAP receiver callback URI — derived from public_url via beckn-router" - }, - { - "key": "bpp_id", - "value": "bpp.example.com", - "description": "BPP subscriber ID (FQDN)" - }, - { - "key": "bpp_uri", - "value": "{{public_url}}/bpp/receiver", - "description": "BPP receiver callback URI — derived from public_url via beckn-router" - }, - { - "key": "networkId", - "value": "beckn.one/testnet", - "description": "Network identifier" - }, - { - "key": "transaction_id", - "value": "", - "description": "Transaction ID — set once in pre-request script, shared across all calls in a flow" - } - ] -} \ No newline at end of file + "info": { + "_postman_id": "71877d77-839a-44ff-942c-d36937d643a1", + "name": "BPP Beckn Starter Kit", + "description": "Usecase-agnostic BPP on_* callbacks for all Beckn Protocol v2.0.0 responses. All schema objects carry JSON-LD @context and @type annotations.\n\nSet bpp_adapter_url = http://localhost:8082/bpp/caller before running.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "8677993" + }, + "item": [ + { + "name": "1 \u2014 Transaction", + "item": [ + { + "name": "on_select", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"context\": {\n \"networkId\": \"beckn.one/testnet\",\n \"action\": \"on_select\",\n \"version\": \"2.0.0\",\n \"bapId\": \"{{bap_id}}\",\n \"bapUri\": \"{{bap_uri}}\",\n \"bppId\": \"{{bpp_id}}\",\n \"bppUri\": \"{{bpp_uri}}\",\n \"transactionId\": \"{{transaction_id}}\",\n \"messageId\": \"{{$guid}}\",\n \"timestamp\": \"{{$isoTimestamp}}\",\n \"ttl\": \"PT30S\"\n },\n \"message\": {\n \"contract\": {\n \"id\": \"draft-contract-0001\",\n \"participants\": [\n {\n \"id\": \"participant-buyer-001\",\n \"descriptor\": {\n \"name\": \"John Doe\",\n \"code\": \"buyer\"\n }\n }\n ],\n \"commitments\": [\n {\n \"id\": \"commitment-001\",\n \"descriptor\": {\n \"name\": \"Basic Service Package\",\n \"code\": \"BSP-001\"\n },\n \"status\": {\n \"code\": \"DRAFT\"\n },\n \"resources\": [\n {\n \"id\": \"resource-001\",\n \"descriptor\": {\n \"name\": \"Basic Service Package\",\n \"code\": \"BSP-001\"\n },\n \"quantity\": {\n \"unitQuantity\": 2,\n \"unitCode\": \"UNIT\"\n }\n }\n ],\n \"offer\": {\n \"id\": \"offer-001\",\n \"resourceIds\": [\n \"resource-001\"\n ]\n }\n }\n ],\n \"consideration\": [\n {\n \"id\": \"consideration-001\",\n \"price\": {\n \"currency\": \"INR\",\n \"value\": \"2000.00\"\n },\n \"status\": {\n \"code\": \"DRAFT\"\n },\n \"breakup\": [\n {\n \"title\": \"Base Price\",\n \"price\": {\n \"currency\": \"INR\",\n \"value\": \"1800.00\"\n }\n },\n {\n \"title\": \"Taxes\",\n \"price\": {\n \"currency\": \"INR\",\n \"value\": \"200.00\"\n }\n }\n ]\n }\n ]\n }\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{bpp_adapter_url}}/on_select", + "host": [ + "{{bpp_adapter_url}}" + ], + "path": [ + "on_select" + ] + }, + "description": "BPP returns updated contract draft with pricing after selection." + }, + "response": [] + }, + { + "name": "on_init", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"context\": {\n \"networkId\": \"beckn.one/testnet\",\n \"action\": \"on_init\",\n \"version\": \"2.0.0\",\n \"bapId\": \"{{bap_id}}\",\n \"bapUri\": \"{{bap_uri}}\",\n \"bppId\": \"{{bpp_id}}\",\n \"bppUri\": \"{{bpp_uri}}\",\n \"transactionId\": \"{{transaction_id}}\",\n \"messageId\": \"{{$guid}}\",\n \"timestamp\": \"{{$isoTimestamp}}\",\n \"ttl\": \"PT30S\"\n },\n \"message\": {\n \"contract\": {\n \"commitments\": [\n {\n \"status\": {\n \"descriptor\": {\n \"code\": \"DRAFT\"\n }\n },\n \"resources\": [\n {\n \"id\": \"resource-001\",\n \"descriptor\": {\n \"name\": \"Basic Service Package\",\n \"code\": \"BSP-001\"\n },\n \"quantity\": {\n \"unitQuantity\": 2,\n \"unitCode\": \"UNIT\"\n }\n }\n ],\n \"offer\": {\n \"id\": \"offer-001\",\n \"resourceIds\": [\n \"resource-001\"\n ]\n }\n }\n ],\n \"participants\": [\n {\n \"id\": \"participant-buyer-001\",\n \"descriptor\": {\n \"name\": \"John Doe\",\n \"code\": \"buyer\"\n }\n }\n ],\n \"performance\": [\n {\n \"id\": \"perf-001\"\n }\n ],\n \"settlements\": [\n {\n \"id\": \"settlement-001\",\n \"status\": \"DRAFT\"\n }\n ]\n }\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{bpp_adapter_url}}/on_init", + "host": [ + "{{bpp_adapter_url}}" + ], + "path": [ + "on_init" + ] + }, + "description": "BPP confirms initialised contract with payment details." + }, + "response": [] + }, + { + "name": "on_confirm", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"context\": {\n \"networkId\": \"beckn.one/testnet\",\n \"action\": \"on_confirm\",\n \"version\": \"2.0.0\",\n \"bapId\": \"{{bap_id}}\",\n \"bapUri\": \"{{bap_uri}}\",\n \"bppId\": \"{{bpp_id}}\",\n \"bppUri\": \"{{bpp_uri}}\",\n \"transactionId\": \"{{transaction_id}}\",\n \"messageId\": \"{{$guid}}\",\n \"timestamp\": \"{{$isoTimestamp}}\",\n \"ttl\": \"PT30S\"\n },\n \"message\": {\n \"contract\": {\n \"id\": \"draft-contract-0001\",\n \"commitments\": [\n {\n \"id\": \"commitment-001\",\n \"status\": {\n \"descriptor\": {\n \"code\": \"DRAFT\"\n }\n },\n \"resources\": [\n {\n \"id\": \"resource-001\",\n \"descriptor\": {\n \"name\": \"Basic Service Package\",\n \"code\": \"BSP-001\"\n },\n \"quantity\": {\n \"unitQuantity\": 2,\n \"unitCode\": \"UNIT\"\n }\n }\n ],\n \"offer\": {\n \"id\": \"offer-001\",\n \"resourceIds\": [\n \"resource-001\"\n ]\n }\n }\n ],\n \"participants\": [\n {\n \"id\": \"participant-buyer-001\",\n \"descriptor\": {\n \"name\": \"John Doe\",\n \"code\": \"buyer\"\n }\n }\n ],\n \"performance\": [\n {\n \"id\": \"perf-001\"\n }\n ],\n \"settlements\": [\n {\n \"id\": \"settlement-001\",\n \"status\": \"COMPLETE\"\n }\n ]\n }\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{bpp_adapter_url}}/on_confirm", + "host": [ + "{{bpp_adapter_url}}" + ], + "path": [ + "on_confirm" + ] + }, + "description": "BPP confirms contract as binding and provides performance details." + }, + "response": [] + } + ] + }, + { + "name": "2 \u2014 Catalog Publishing", + "item": [ + { + "name": "publish", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"context\": {\n \"action\": \"catalog/publish\"\n },\n \"message\": {\n \"catalogs\": [\n {\n \"id\": \"staging.p-node.fabric.nfh.global/CAT-GENERIC-001\",\n \"descriptor\": {\n \"name\": \"Generic Catalog\",\n \"shortDesc\": \"Daily essentials generic items\"\n },\n \"provider\": {\n \"id\": \"PROV-EXAMPLE-01\",\n \"descriptor\": {\n \"name\": \"BP Pvt Ltd\"\n },\n \"availableAt\": [\n {\n \"geo\": {\n \"type\": \"Point\",\n \"coordinates\": [\n 77.6401,\n 12.9116\n ]\n },\n \"address\": {\n \"streetAddress\": \"27th Main Rd, Sector 2, HSR Layout\",\n \"addressLocality\": \"Bengaluru\",\n \"addressRegion\": \"Karnataka\",\n \"postalCode\": \"560102\",\n \"addressCountry\": \"IN\"\n }\n }\n ]\n },\n \"resources\": [\n {\n \"id\": \"ITEM-GENERIC-001\",\n \"descriptor\": {\n \"name\": \"Bru Gold Instant Coffee Powder\",\n \"shortDesc\": \"Rich and aromatic instant coffee powder, 200g\",\n \"longDesc\": \"Bru Gold is a premium blend of coffee and chicory, crafted for those who love a rich, flavourful cup. Made from handpicked coffee beans roasted to perfection, this instant coffee powder dissolves quickly to give you a smooth, aromatic brew every time. Whether you prefer your coffee black or with milk, Bru Gold delivers a satisfying coffee experience. Perfect for filter coffee, cappuccino, or a classic South Indian style coffee.\",\n \"mediaFile\": [\n {\n \"uri\": \"https://www.jiomart.com/images/product/original/599173064/599173064.jpg\",\n \"mimeType\": \"image/jpeg\",\n \"label\": \"Product image\"\n }\n ]\n },\n \"provider\": {\n \"id\": \"PROV-EXAMPLE-01\",\n \"descriptor\": {\n \"name\": \"BP Pvt Ltd\"\n }\n },\n \"rating\": {\n \"ratingValue\": 4.1,\n \"ratingCount\": 18200,\n \"bestRating\": 5,\n \"worstRating\": 1\n },\n \"availableAt\": [\n {\n \"geo\": {\n \"type\": \"Point\",\n \"coordinates\": [\n 77.6401,\n 12.9116\n ]\n },\n \"address\": {\n \"streetAddress\": \"27th Main Rd, Sector 2, HSR Layout\",\n \"addressLocality\": \"Bengaluru\",\n \"addressRegion\": \"Karnataka\",\n \"postalCode\": \"560102\",\n \"addressCountry\": \"IN\"\n }\n }\n ]\n },\n {\n \"id\": \"ITEM-GENERIC-002\",\n \"descriptor\": {\n \"name\": \"Generic Classic Item\",\n \"shortDesc\": \"Pure soluble coffee, bold and rich, 100g\",\n \"longDesc\": \"Nescafe Classic is made from 100% pure coffee beans, carefully selected and roasted to deliver a bold, rich flavour in every cup. This instant coffee dissolves instantly in hot or cold water, making it ideal for a quick coffee fix anytime. The deep roast brings out natural coffee aromas that energise your mornings. Loved by coffee enthusiasts across India, Nescafe Classic is the trusted choice for a consistently great cup of coffee.\",\n \"mediaFile\": [\n {\n \"uri\": \"https://m.media-amazon.com/images/S/aplus-media/vc/bb6a0196-cad0-4395-b85e-134ef725c0f7._CR0,0,1251,1251_PT0_SX300__.png\",\n \"mimeType\": \"image/png\",\n \"label\": \"Product image\"\n }\n ]\n },\n \"provider\": {\n \"id\": \"PROV-EXAMPLE-02\",\n \"descriptor\": {\n \"name\": \"Mart Pvt Ltd\"\n }\n },\n \"rating\": {\n \"ratingValue\": 4.1,\n \"ratingCount\": 32500,\n \"bestRating\": 5,\n \"worstRating\": 1\n },\n \"availableAt\": [\n {\n \"geo\": {\n \"type\": \"Point\",\n \"coordinates\": [\n 76.6394,\n 12.2958\n ]\n },\n \"address\": {\n \"streetAddress\": \"12 Sayyaji Rao Road, Devaraja Market\",\n \"addressLocality\": \"Mysore\",\n \"addressRegion\": \"Karnataka\",\n \"postalCode\": \"570001\",\n \"addressCountry\": \"IN\"\n }\n }\n ]\n }\n ],\n \"offers\": [\n {\n \"id\": \"OFFER-GENERIC-002\",\n \"descriptor\": {\n \"name\": \"Generic Bundle\",\n \"shortDesc\": \"Bru Gold + Nescafe Classic at 15% off - your morning coffee essentials\"\n },\n \"resourceIds\": [\n \"ITEM-GENERIC-001\"\n ],\n \"provider\": {\n \"id\": \"PROV-EXAMPLE-01\",\n \"descriptor\": {\n \"name\": \"Mart Pvt Ltd\"\n }\n },\n \"validity\": {\n \"startDate\": \"2026-03-04T00:00:00Z\",\n \"endDate\": \"2026-03-31T23:59:59Z\"\n }\n },\n {\n \"id\": \"OFFER-GROCERY-STAPLES-SAVER-002\",\n \"descriptor\": {\n \"name\": \"Hot Beverages Pack\",\n \"shortDesc\": \"Nescafe Classic + Tata Tea Premium combo - 10% off on your daily beverages\"\n },\n \"resourceIds\": [\n \"ITEM-GENERIC-002\"\n ],\n \"provider\": {\n \"id\": \"PROV-EXAMPLE-01\",\n \"descriptor\": {\n \"name\": \"Mart Pvt Ltd\"\n }\n },\n \"validity\": {\n \"startDate\": \"2026-03-04T00:00:00Z\",\n \"endDate\": \"2026-03-31T23:59:59Z\"\n }\n }\n ]\n }\n ],\n \"publishDirectives\": [\n {\n \"catalogId\": \"staging.p-node.fabric.nfh.global/CAT-GENERIC-001\",\n \"visibleTo\": [\n \"beckn.one/testnet\",\n \"nfh.global/testnet\"\n ],\n \"catalogType\": \"REGULAR\"\n }\n ]\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{bpp_base_url}}/catalog/publish", + "host": [ + "{{bpp_base_url}}" + ], + "path": [ + "catalog", + "publish" + ] + }, + "description": "DS-internal trigger for the catalog-publisher plugin: request body matches beckn.yaml's real CatalogPublishAction envelope shape (context/message.catalogs[]/message.publishDirectives[]) so schema validation runs against the real request, no synthesized wrapper. publishDirectives[].visibleTo maps onto each catalog's networkIds in the published index (empty/omitted = public). Diffs each catalog against what was last published (baseline vs. incremental change file), signs the result, and writes it under the handler's outputRoot. Unsigned -- same-operator call, not a signed network action. Response: {status: COMPLETED, results: [{catalogId, status: ACCEPTED|REJECTED, version, reason}]} or {status: FAILED, error: {...}} on a fatal failure." + }, + "response": [] + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "requests": {}, + "exec": [ + "// Beckn Protocol v2.0.0 \u2014 BPP Generic Collection", + "// Pre-request: set transaction_id to match the BAP collection for end-to-end testing", + "if (!pm.collectionVariables.get('transaction_id')) {", + " pm.collectionVariables.set('transaction_id', pm.variables.replaceIn('{{$guid}}'));", + "}" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "requests": {}, + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});", + "pm.test('Response has ack', function () {", + " const json = pm.response.json();", + " pm.expect(json).to.have.property('message');", + " pm.expect(json.message).to.have.property('ack');", + " pm.expect(json.message.ack.status).to.eql('ACK');", + "});" + ] + } + } + ], + "variable": [ + { + "key": "bpp_adapter_url", + "value": "http://localhost:8082/bpp/caller", + "description": "BAP adapter caller endpoint (outbound)" + }, + { + "key": "bap_id", + "value": "bap.example.com", + "description": "BAP subscriber ID (FQDN)" + }, + { + "key": "public_url", + "value": "http://beckn-router:9000", + "description": "Base URL for bapUri / bppUri callbacks. Default routes within Docker via beckn-router. For external services (discover/publish): change to https://your-domain.ngrok-free.app" + }, + { + "key": "bap_uri", + "value": "{{public_url}}/bap/receiver", + "description": "BAP receiver callback URI \u2014 derived from public_url via beckn-router" + }, + { + "key": "bpp_id", + "value": "bpp.example.com", + "description": "BPP subscriber ID (FQDN)" + }, + { + "key": "bpp_uri", + "value": "{{public_url}}/bpp/receiver", + "description": "BPP receiver callback URI \u2014 derived from public_url via beckn-router" + }, + { + "key": "networkId", + "value": "beckn.one/testnet", + "description": "Network identifier" + }, + { + "key": "transaction_id", + "value": "", + "description": "Transaction ID \u2014 set once in pre-request script, shared across all calls in a flow" + }, + { + "key": "bpp_base_url", + "value": "http://localhost:8082", + "description": "BPP adapter host:port, no path suffix -- used by root-level paths like /catalog/publish." + } + ] +} diff --git a/schemas/Beckn_catalog_index.json b/schemas/Beckn_catalog_index.json new file mode 100644 index 0000000..75a88e9 --- /dev/null +++ b/schemas/Beckn_catalog_index.json @@ -0,0 +1,182 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://schema.beckn.org/dedi/beckn-catalog-index-schema.json", + "title": "Beckn Catalog Index", + "description": "A publisher's catalog index: a plain Beckn file (not a DeDi file, and not required to be signed as a whole -- trust rides on each catalog file's own per-entry signature). Lists every catalog the publisher has, each carrying identity, lifecycle status, visibility, and either a baseline + change-file chain (ACTIVE) or a tombstone (RETIRED).", + "type": "object", + "additionalProperties": false, + "required": ["participantId", "version", "catalogs"], + "properties": { + "participantId": { + "type": "string", + "description": "The publisher's identity -- a domain (e.g. \"open-economy.nfh.global\"). Every catalogId in this index is scoped under it." + }, + "version": { + "type": "integer", + "minimum": 1, + "description": "Monotonically increasing version counter for the index as a whole -- a crawler's cursor. MUST NOT decrease between publishes; distinct from any one catalog's own baseline/change-file versions." + }, + "next_update": { + "type": "string", + "format": "date-time", + "description": "Bounds how long any cached copy of this index may be believed; a crawler past this MUST re-fetch before relying on it." + }, + "catalogs": { + "type": "array", + "description": "One entry per catalogId the publisher has ever published, including retired ones (kept as tombstones so a crawler can distinguish \"gone\" from \"never existed\").", + "items": { "$ref": "#/$defs/catalogEntry" } + } + }, + "$defs": { + "catalogEntry": { + "type": "object", + "required": ["catalogId", "status"], + "properties": { + "catalogId": { + "type": "string", + "description": "Unique identifier of the catalog, participant-scoped (e.g. \"open-economy.nfh.global/electronics-2026\")." + }, + "status": { + "type": "string", + "enum": ["ACTIVE", "RETIRED"], + "description": "Lifecycle state. RETIRED is a tombstone -- no files, no baseline, no changes." + } + }, + "if": { + "properties": { "status": { "const": "RETIRED" } } + }, + "then": { + "additionalProperties": false, + "required": ["catalogId", "status", "retiredAt"], + "properties": { + "catalogId": true, + "status": true, + "retiredAt": { + "type": "string", + "format": "date-time", + "description": "When this catalog was retired." + } + } + }, + "else": { + "additionalProperties": false, + "required": ["catalogId", "status", "catalogType", "baseline"], + "properties": { + "catalogId": true, + "status": true, + "catalogType": { + "type": "string", + "enum": ["MASTER", "REGULAR"], + "description": "Whether this is a MASTER catalog that other REGULAR catalogs may extend, or a REGULAR catalog." + }, + "networkIds": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1, + "description": "Networks this catalog is restricted to. Absent or omitted means public -- there is no explicit \"public\" marker, the field is simply not present." + }, + "authMethods": { + "type": "array", + "items": { "$ref": "#/$defs/authMethod" }, + "description": "How a crawler authenticates a restricted download. Only meaningful when networkIds is present. A list, not a single value, so new methods extend without changing the shape." + }, + "schemaTypes": { + "type": "array", + "items": { "type": "string", "format": "uri" }, + "description": "Domain-specific JSON-LD context/schema URLs the catalog's resources and offers conform to. Optional -- omit for a catalog with no domain-specific extension." + }, + "crawlHint": { + "type": "string", + "enum": ["always", "hourly", "daily", "weekly", "monthly", "yearly", "never"], + "description": "A suggested crawl frequency, like a sitemap's changefreq. Purely advisory -- a crawler stays in control of its own schedule and budget; this hint may be honored for fast-moving catalogs, never treated as authoritative." + }, + "baseline": { "$ref": "#/$defs/fileEntry" }, + "changes": { + "type": "array", + "items": { "$ref": "#/$defs/fileEntry" }, + "description": "Change files since the baseline, in version order. May include a lightweight forward-pointer entry carrying only \"version\" (no url/digest/signature yet), signaling a newer version exists before its file is ready to serve." + } + } + } + }, + "fileEntry": { + "type": "object", + "additionalProperties": false, + "required": ["version"], + "properties": { + "version": { + "type": "integer", + "minimum": 1, + "description": "This file's version. Monotonic within its catalogId, independent of the index's own version." + }, + "url": { + "type": "string", + "format": "uri", + "description": "Absolute, immutable URL of this file. A new version is always a new URL -- nothing is overwritten in place." + }, + "size": { + "type": "integer", + "minimum": 0, + "description": "Byte size of the file at url. Required whenever url/digest/signature are present -- part of the signed tuple (see signature), and what makes the fetch-baseline-instead cutover rule computable before downloading anything." + }, + "digest": { + "type": "string", + "pattern": "^sha-256:[0-9a-f]{64}$", + "description": "SHA-256 digest of the file at url, prefixed with the algorithm name." + }, + "signature": { "$ref": "#/$defs/signature" } + }, + "dependencies": { + "url": ["size", "digest", "signature"], + "size": ["url", "digest", "signature"], + "digest": ["url", "size", "signature"], + "signature": ["url", "size", "digest"] + } + }, + "signature": { + "type": "object", + "additionalProperties": false, + "required": ["keyId", "value", "validUntil"], + "properties": { + "keyId": { + "type": "string", + "description": "Identifies the publisher key this signature was made with (matches a kid in the manifest's keys[])." + }, + "value": { + "type": "string", + "description": "Signature value. Ed25519 (or ES256) over the JCS canonicalization of {catalogId, version, url, size, digest, validUntil} for the file entry this signature is attached to -- a tuple, not a bare hash, so it cannot be replayed for a different file, version, or past its validity window. May equally be encoded as a detached JWS instead of a plain signature value; the encoding is a schema decision, not a semantic one." + }, + "validUntil": { + "type": "string", + "format": "date-time", + "description": "Caps how long this signed entry may be presented, independent of the index's own next_update." + } + } + }, + "authMethod": { + "type": "object", + "additionalProperties": false, + "required": ["method", "header", "signedHeaders", "freshnessSeconds"], + "properties": { + "method": { + "type": "string", + "description": "e.g. \"signed-request\" -- self-describing; a crawler reads this object and knows what to build. Extend the enum as new methods are added; do not change this shape to add one." + }, + "header": { + "type": "string", + "description": "The HTTP header the crawler's signature goes in (e.g. \"Authorization\")." + }, + "signedHeaders": { + "type": "array", + "items": { "type": "string" }, + "description": "The standard Beckn HTTP Signature's signed pseudo-headers/headers (e.g. [\"(created)\", \"(expires)\", \"(request-target)\", \"host\", \"digest\"]). No custom challenge fields -- the signature standard's own request binding is what ties a signature to one URL. The verification algorithm itself is never declared here; a gateway takes it from the requester's registered key." + }, + "freshnessSeconds": { + "type": "integer", + "minimum": 1, + "description": "Replay bound: how long after (created) a signed request remains acceptable. 60 is the spec's suggested default for a machine-to-machine fetch." + } + } + } + } +}