A fully dynamic Beckn BPP (Buyer-side Protocol Provider) simulator for the EV charging network domain. Unlike template-based mocks, this service generates every on_* response at runtime from the incoming request — new order IDs, payment references, and telemetry on every call.
| Responsibility | Detail |
|---|---|
| Receives Beckn BPP requests | discover, search, select, init, confirm, status, track, cancel, update, rating, support |
| Responds synchronously | Returns ACK immediately on every valid request |
| Sends async callbacks | Fires on_<action> to the ONIX adapter BPP caller endpoint after a configurable delay |
| Maintains order state | In-memory state machine tracks order lifecycle across a transaction (CREATED → INITIATED → CHARGING → COMPLETED) |
| Simulates real telemetry | on_status returns live-computed SOC %, power (kW), energy (kWh), voltage, current based on time elapsed since confirm |
BAP Application
│
▼
BAP-ONIX Adapter (onix-bap-plugin :8001)
│ signs request, routes to BPP side
▼
BPP-ONIX Adapter (onix-bpp-plugin :8002)
│ validates signature, validates schema, routes to backend
▼
mock-bpp-dynamic (:9002) ← THIS SERVICE
│ generates on_* response
│ POST /bpp/caller/on_<action>
▼
BPP-ONIX Adapter (onix-bpp-plugin :8002)
│ signs response, routes back to BAP
▼
BAP-ONIX Adapter (onix-bap-plugin :8001)
│ delivers to BAP receiver
▼
BAP Application
The mock-bpp sits behind the ONIX BPP adapter. It never talks directly to the BAP — all traffic flows through the adapter.
BAP ONIX-BAP ONIX-BPP mock-bpp
│ │ │ │
│── discover ───────►│ │ │
│ │── /bpp/receiver/discover ──────────►│
│ │ │ ACK ◄───────────│
│ │ │ │── (async, 500ms delay)
│ │◄─── /bap/receiver/on_discover ◄─────│ catalog: 5 stations × 17 connectors
│◄── on_discover ────│ │ │
│ │ │ │
│── select ─────────►│ │ │
│ │── /bpp/receiver/select ─────────────►│
│ │ │ ACK ◄───────────│
│ │◄─── /bap/receiver/on_select ◄────────│ quoted price + buyer-finder-fee
│◄── on_select ──────│ │ │
│ │ │ │
│── init ───────────►│ │ │
│ │── /bpp/receiver/init ───────────────►│
│ │ │ ACK ◄───────────│
│ │◄─── /bap/receiver/on_init ◄──────────│ new orderId, paymentId, txnRef, payment URL
│◄── on_init ────────│ │ │
│ │ │ │
│── confirm ────────►│ │ │
│ │── /bpp/receiver/confirm ────────────►│ starts state-machine clock
│ │ │ ACK ◄───────────│
│ │◄─── /bap/receiver/on_confirm ◄───────│ CONFIRMED + payment COMPLETED
│◄── on_confirm ─────│ │ │
│ │ │ │
│── status ─────────►│ │ │
│ │── /bpp/receiver/status ─────────────►│
│ │◄─── /bap/receiver/on_status ◄────────│ live telemetry (SOC, kWh, kW …)
│◄── on_status ──────│ │ │
Transaction ID is generated once by the BAP and echoed through every hop unchanged.
Message ID is freshly generated by mock-bpp for every callback.
Order IDs / Payment IDs are UUID-based, generated fresh at init and confirm.
| Action | What mock-bpp generates |
|---|---|
discover |
Catalog of 5 fixed EV stations (Bengaluru–Mysuru corridor) with 17 connector slots. Validity timestamps are fresh on every call |
search |
Same catalog as discover |
select |
Echoes buyer + items from request, computes: base cost + 20% surge + 15% discount + fees + 2.5% BFF |
init |
New orderId (UUID), orderNumber, paymentId, txnRef, payment URL. beckn:paymentStatus: INITIATED |
confirm |
Reuses orderId from request if present, generates new paymentId + txnRef. Starts the state-machine clock. beckn:paymentStatus: COMPLETED |
status |
Reads state machine: INITIATED (0–10s) → CHARGING (10–120s) → COMPLETED (>120s). Returns live telemetry metrics |
track |
Returns current GPS-like location of charger |
cancel |
Marks order CANCELLED in state store |
update |
Echoes updated order back |
rating |
Returns feedback submission confirmation |
support |
Returns support ticket reference |
Config file path is resolved in this order:
CONFIG_FILEenvironment variableconfig/config.yaml(relative to binary)/app/config/config.yaml(Docker default)
server:
port: ":9002" # TCP port the HTTP server listens on
defaults:
version: "1.0.0" # Beckn protocol version echoed in context
domain: "beckn.one:deg:ev-charging"
country_code: "IND"
city_code: "std:080"
bpp_id: "my-bpp.example.com" # Identifier of this BPP in the Beckn network
bpp_uri: "http://mock-bpp:9002" # Publicly reachable URI of this BPP
callback:
enabled: true # Master switch for all async on_* callbacks
delay_ms: 500 # Simulated processing delay before callback (ms)
timeout_ms: 10000 # HTTP client timeout for the callback POST (ms)
base_uri: "http://onix-bpp-plugin:8002"
# Callback is sent to: {base_uri}/bpp/caller/on_{action}
# Per-action enable/disable (missing key = enabled by default)
actions:
discover: true
search: true
select: true
init: true
confirm: true
status: true
track: true
cancel: true
update: true
rating: true
support: true
overrides:
file: "" # Optional path to a JSON file for static response overrides
# Format: { "init": { "message": {...} }, "confirm": { "message": {...} } }
# If an action key exists, it replaces dynamic generation for that action onlyThese override the config file and are useful for container deployments where you inject secrets/endpoints as env vars:
| Variable | Overrides | Example |
|---|---|---|
CONFIG_FILE |
Config file path | /app/config/config.yaml |
BPP_ID |
defaults.bpp_id |
ev-charging.sandbox2.com |
BPP_URI |
defaults.bpp_uri |
http://onix-bpp-plugin:8002/bpp/receiver |
CALLBACK_BASE_URI |
callback.base_uri |
http://onix-bpp-plugin:8002 |
git clone https://github.com/beckn-one/mock-bpp
cd mock-bpp
# Edit config
cp config/config.yaml config/local.yaml
# Set callback.base_uri to your local ONIX BPP adapter address
CONFIG_FILE=config/local.yaml go run ./cmd/server/main.goHealth check: curl http://localhost:9002/health
docker build -t mock-bpp-dynamic:latest .
docker run -d \
--name mock-bpp \
-p 9002:9002 \
-v $(pwd)/config/config.yaml:/app/config/config.yaml:ro \
-e CALLBACK_BASE_URI=http://host.docker.internal:8002 \
mock-bpp-dynamic:latestThe sandbox docker-compose.yml already wires mock-bpp into the full ONIX network:
mock-bpp:
image: mock-bpp-dynamic:latest
pull_policy: never
container_name: mock-bpp
ports:
- "9002:9002"
volumes:
- ./mock-bpp_config.yml:/app/config/config.yaml:ro
environment:
CONFIG_FILE: /app/config/config.yaml
networks:
- onix-networkKey config values for Docker Compose:
# mock-bpp_config.yml
defaults:
bpp_id: "ev-charging.sandbox2.com"
bpp_uri: "http://onix-bpp-plugin:8002/bpp/receiver"
callback:
base_uri: "http://onix-bpp-plugin:8002"
# Uses Docker Compose service name — works because both are on onix-networkTo rebuild after a code change:
# In mock-bpp repo
docker compose build --no-cache mock-bpp
# In sandbox
cd ubc-ev-sandbox/sandbox
docker compose up -d --no-deps --force-recreate mock-bppConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: mock-bpp-config
data:
config.yaml: |
server:
port: ":9002"
defaults:
bpp_id: "ev-charging.prod.example.com"
bpp_uri: "http://onix-bpp-plugin.beckn.svc.cluster.local:8002/bpp/receiver"
domain: "beckn.one:deg:ev-charging"
version: "1.0.0"
country_code: "IND"
city_code: "std:080"
callback:
enabled: true
delay_ms: 500
timeout_ms: 10000
base_uri: "http://onix-bpp-plugin.beckn.svc.cluster.local:8002"Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: mock-bpp
namespace: beckn
spec:
replicas: 1
selector:
matchLabels:
app: mock-bpp
template:
metadata:
labels:
app: mock-bpp
spec:
containers:
- name: mock-bpp
image: mock-bpp-dynamic:latest
imagePullPolicy: Never # use IfNotPresent for registry images
ports:
- containerPort: 9002
env:
- name: CONFIG_FILE
value: /app/config/config.yaml
# Override individual values without rebuilding:
- name: BPP_ID
valueFrom:
configMapKeyRef:
name: mock-bpp-config
key: bpp_id # or use a Secret
- name: CALLBACK_BASE_URI
value: "http://onix-bpp-plugin.beckn.svc.cluster.local:8002"
volumeMounts:
- name: config
mountPath: /app/config
volumes:
- name: config
configMap:
name: mock-bpp-configService:
apiVersion: v1
kind: Service
metadata:
name: mock-bpp
namespace: beckn
spec:
selector:
app: mock-bpp
ports:
- port: 9002
targetPort: 9002Note on
callback.base_uriin Kubernetes: Use the cluster-internal DNS name of the ONIX BPP adapter service:http://<service-name>.<namespace>.svc.cluster.local:<port>
For UAT or cloud deployments where you don't want to rebuild the image, override everything via env vars and mount only the config file:
docker run -d \
--name mock-bpp \
-p 9002:9002 \
-v /etc/mock-bpp/config.yaml:/app/config/config.yaml:ro \
-e BPP_ID="ev-charging.uat.example.com" \
-e BPP_URI="https://onix-bpp.uat.example.com/bpp/receiver" \
-e CALLBACK_BASE_URI="https://onix-bpp.uat.example.com" \
mock-bpp-dynamic:latest| Method | Path | Description |
|---|---|---|
GET |
/health |
Health check — returns {"status":"healthy"} |
POST |
/discover |
Receive discover from CDS/BAP |
POST |
/search |
Receive search |
POST |
/select |
Receive select |
POST |
/init |
Receive init — generates draft order |
POST |
/confirm |
Receive confirm — starts charging session clock |
POST |
/status |
Receive status — returns live telemetry |
POST |
/track |
Receive track |
POST |
/cancel |
Receive cancel — marks order CANCELLED |
POST |
/update |
Receive update |
POST |
/rating |
Receive rating |
POST |
/support |
Receive support |
Every endpoint:
- Returns 200 ACK synchronously (or 400 NACK on bad JSON)
- Fires
POST {callback.base_uri}/bpp/caller/on_<action>asynchronously afterdelay_ms
{callback.base_uri}/bpp/caller/on_{action}
Examples:
| Incoming action | Callback sent to |
|---|---|
/init |
http://onix-bpp-plugin:8002/bpp/caller/on_init |
/confirm |
http://onix-bpp-plugin:8002/bpp/caller/on_confirm |
/status |
http://onix-bpp-plugin:8002/bpp/caller/on_status |
The ONIX BPP adapter (bppTxnCaller module) receives these, signs them, and forwards to the BAP.
The in-memory state store (internal/state) tracks each Beckn transaction_id:
select / init
│
▼
CREATED
│
confirm
▼
INITIATED ──── (0–10s after confirm)
│
▼
CHARGING ──── (10–120s) live telemetry computed from elapsed time
│
▼
COMPLETED ──── (>120s)
│
cancel (any time)
▼
CANCELLED
State is in-memory only — it resets on container restart. This is intentional for a mock/UAT service. For persistent state across restarts, mount a volume or integrate a store.
If you need a specific action to return a fixed payload (e.g., to test an error scenario or a particular edge case) without changing code:
# config.yaml
overrides:
file: "/app/config/overrides.json"// overrides.json
{
"init": {
"message": {
"order": {
"@context": "...",
"@type": "beckn:Order",
"beckn:orderStatus": "FAILED"
}
}
}
}Only actions present in the overrides file are affected. All other actions continue to use dynamic generation.
mock-bpp/
├── cmd/server/main.go Entry point — wires config, store, generator, handler
├── config/config.yaml Default configuration
├── internal/
│ ├── config/config.go Config loader (YAML + env var overrides)
│ ├── model/beckn.go Beckn protocol types (Context, BecknRequest, AckResponse …)
│ ├── state/store.go In-memory order state machine
│ ├── handler/handler.go HTTP handlers — ACK/NACK + async dispatch
│ ├── callback/sender.go Async on_* callback sender
│ └── generator/
│ ├── generator.go Dispatcher + shared helpers (mapVal, strFromMap …)
│ ├── discover.go Catalog: 5 EV stations × 17 connectors
│ ├── search.go Same as discover
│ ├── select.go Price quote from request items
│ ├── init.go Draft order: new orderId / paymentId / txnRef
│ ├── confirm.go Confirmed order + starts state clock
│ ├── status.go Live telemetry from state machine
│ ├── track.go Location response
│ ├── cancel.go Marks order CANCELLED
│ ├── update.go Echo updated order
│ ├── rating.go Feedback confirmation
│ └── support.go Support ticket reference
├── Dockerfile
└── docker-compose.yml
| Package | Purpose |
|---|---|
github.com/google/uuid |
UUID generation for order/payment IDs |
gopkg.in/yaml.v3 |
Config file parsing |
| Go standard library only | HTTP server, JSON, logging |
No database. No message queue. No external services. The binary is self-contained.