POS card terminal — CodePay (ECR Hub)
Executive summary
Section titled “Executive summary”CodePay integration in LionPOS uses the same POS bridge WebSocket and activation flow as other terminal tooling: clients pair via pos-bridge/activate, then exchange JSON messages whose shape follows CodePay’s ECR Hub contract (ecrhub.* topics). The monorepo implements this in @indochina/ecr-hub — package path packages/ecr-hub — via CodepayAdapter, encoder/parser helpers under manufacturers/codepay/, and shared constants (also re-exported from @indochina/shared for backwards compatibility).
Official reference (Kotlin samples, topic names, demo app_id): codepay-us/codepay-regsiter-same-terminal-integration-demo.
For platform-wide behaviour (payment intent, polling, terminal-webhook, bridge pairing), see POS card terminals & ECR Hub (PAX focus).
Transport and activation
Section titled “Transport and activation”| Concern | Detail |
|---|---|
| Channel | WebSocket — same wsUrl from POST /api/v1/pos-bridge/activate (or vendor dashboard activate) as the generic POS bridge; path /api/v1/pos-bridge/ws with terminalId + token query params. |
| Heartbeat | Clients may send bridge_heartbeat so the server keeps store_devices.is_connected accurate; the CodePay lab does this while testing. |
| Outcome to SaaS | Production bridges still turn terminal success/failure into POST /api/v1/pos/payments/terminal-webhook with x-pos-terminal-key — see POS and payment webhooks. |
Server ECR relay (ecr_relay_request / ecr_relay_response): when the SaaS backend dispatches through EcrHubService with createBridgeRelayTransport, the POS bridge carries opaque hub payloads to vendor-desktop, which opens the CodePay service WebSocket (relayTransportKind: "ws"). Those socket JSON bodies are still ecrhub.* envelopes as below; they are not re-wrapped except for the outer bridge message type and correlationId. Manufacturer is ECR_MFR.CODEPAY in application code. See the bridge-relay matrix and sequence in POS card terminals & ECR Hub (PAX focus) (section Bridge relay).
Message envelope
Section titled “Message envelope”Each outbound request is a JSON object with (at minimum):
| Field | Role |
|---|---|
version | Protocol version — constants CODEPAY_ECR_HUB_VERSION_1_1 ("1.1") or CODEPAY_ECR_HUB_VERSION_2_0 ("2.0"). |
app_id | Merchant / integration id from CodePay (device settings in vendor dashboard). The demo repo uses a sample id also exposed as CODEPAY_ECR_HUB_DEMO_APP_ID in code — replace in production. |
topic | ECR Hub topic (see table below). |
request_id | Correlates request/response; the hub uses the command’s correlationId. |
biz_data | Topic-specific payload (e.g. trans_type, amounts, merchant_order_no). |
Encoder implementation: packages/ecr-hub/src/manufacturers/codepay/codepay-encoder.ts.
Topics (ecrhub.*)
Section titled “Topics (ecrhub.*)”| Constant (code) | topic value | Typical use |
|---|---|---|
CODEPAY_ECR_HUB_TOPIC_PAY | ecrhub.pay.order | Sale, refund, void (via trans_type in biz_data). |
CODEPAY_ECR_HUB_TOPIC_TIP_ADJUSTMENT | ecrhub.pay.tip.adjustment | Post-auth tip adjustment. |
CODEPAY_ECR_HUB_TOPIC_BATCH_CLOSE | ecrhub.pay.batch.close | Batch close / settlement housekeeping. |
CODEPAY_ECR_HUB_TOPIC_QUERY | ecrhub.pay.query | Query / report-style lookup (REPORT in the adapter maps here). |
CODEPAY_ECR_HUB_TOPIC_REPRINT | ecrhub.pay.reprint | Reprint last receipt (PRINTER in the adapter). |
trans_type (pay order)
Section titled “trans_type (pay order)”String codes align with the official demo’s invoke constants:
| Constant | Value | Meaning |
|---|---|---|
CODEPAY_TRANS_TYPE_PURCHASE | 1 | Purchase / sale. |
CODEPAY_TRANS_TYPE_VOID | 2 | Void. |
CODEPAY_TRANS_TYPE_REFUND | 3 | Refund / return. |
CODEPAY_TRANS_TYPE_PRE_AUTH | 4 | Pre-authorization (constants exist; CodepayAdapter does not expose PRE_AUTH today — lab samples may build raw envelopes). |
CODEPAY_TRANS_TYPE_PRE_AUTH_COMPLETE | 6 | Complete pre-auth. |
CODEPAY_TRANS_TYPE_QUERY | 21 | Query. |
CODEPAY_TRANS_TYPE_REPRINT | 22 | Reprint. |
CODEPAY_TRANS_TYPE_BATCH_CLOSE | 23 | Batch close. |
CODEPAY_TRANS_TYPE_TIP_ADJUSTMENT | 24 | Tip adjustment. |
pay_scenario
Section titled “pay_scenario”Default in encoders: CODEPAY_PAY_SCENARIO_SWIPE_CARD (SWIPE_CARD). Other supported scenario constants include SCANQR_PAY, BSCANQR_PAY, CASH_PAY — pass via encoder payScenario options when wiring a custom transport.
Response parsing and trans_status
Section titled “Response parsing and trans_status”packages/ecr-hub/src/manufacturers/codepay/codepay-parser.ts normalizes terminal JSON into EcrNormalizedPayment fields used across the hub (auth code, trace/reference, card hints, tip cents, etc.). CodePay returns two common envelope shapes:
- Legacy ECR Hub — top-level
{ version, app_id, topic, request_id, biz_data }with no top-levelresponse_code/response_msg. Approval followsbiz_data.trans_status === "1"only (see constants below). - App-mode (e.g.
callAppMode) — top-levelresponse_code,response_msg(orresponse_message), plusbiz_data. Live terminals often returnresponse_code: "0"andresponse_msg: "SUCCESS"together withbiz_data.trans_status: "2"for a completed sale. The parser treats approval as zero host response code, no explicit decline phrase in the host message, and either a success-likeresponse_msg(SUCCESS/APPROVED/OK) or terminal-completetrans_statusof"1"or"2".trans_status3/4still mean cancelled / pending and are never approved.
| Value | Constant | Legacy biz_data-only envelope | App-mode + host fields |
|---|---|---|---|
1 | CODEPAY_TRANS_STATUS_SUCCESS | Approved | Completed / success |
2 | CODEPAY_TRANS_STATUS_FAIL | Declined / failed (legacy) | Often completed sale when paired with response_code: "0" and response_msg: "SUCCESS" |
3 | CODEPAY_TRANS_STATUS_CANCEL | Cancelled | Cancelled |
4 | CODEPAY_TRANS_STATUS_PENDING | Pending | Pending |
CodepayAdapter — supported canonical actions
Section titled “CodepayAdapter — supported canonical actions”CodepayAdapter requires options.appId. Default timeouts: 90s for payment flows, 30s for housekeeping (BATCH_CLOSE, REPORT, PRINTER).
EcrHubCommand.action | Maps to |
|---|---|
SALE | ecrhub.pay.order + purchase trans_type; optional metadata onScreenTip, onScreenSignature, description. |
RETURN | ecrhub.pay.order + refund; requires reference as original merchant_order_no; optional tipAmountCents. |
VOID_SALE / VOID_RETURN | ecrhub.pay.order + void; requires reference as orig_merchant_order_no. |
TIP_ADJUSTMENT | ecrhub.pay.tip.adjustment. |
BATCH_CLOSE | ecrhub.pay.batch.close. |
REPORT | ecrhub.pay.query (uses merchant_order_no from reference / correlationId). |
PRINTER | ecrhub.pay.reprint. |
Not implemented on the adapter today: SIGN, ABORT, and PRE_AUTH / pre-auth complete — use PAX or raw envelope construction only if the device contract requires it.
Import: @indochina/ecr-hub/codepay or @indochina/ecr-hub.
Vendor dashboard and web lab
Section titled “Vendor dashboard and web lab”| Area | Location |
|---|---|
| Device record | Vendor → Settings → Devices — set manufacturer to CodePay and configure app id (and LAN fields if applicable). |
| WebSocket lab | /dashboard/settings/codepay-ws — connect with wsUrl, send envelopes, inspect responses (developer tooling). |
| Sample payloads | apps/vendor-web/src/lib/codepay-ws-lab-samples.ts — includes pre-auth style samples for manual testing (may not match CodepayAdapter surface). |
Architecture (high level)
Section titled “Architecture (high level)”flowchart LR
subgraph browser["Vendor web"]
Lab["CodePay WS lab\n/settings/codepay-ws"]
Future["Future POS / bridge"]
end
subgraph bridge["POS bridge WebSocket"]
WS["/api/v1/pos-bridge/ws"]
end
subgraph device["CodePay terminal / app"]
ECR["ECR Hub runtime"]
end
Lab -->|"JSON ecrhub.*"| WS
Future -->|"JSON ecrhub.*"| WS
WS <--> ECR
Implementation checklist (CodePay-specific)
Section titled “Implementation checklist (CodePay-specific)”- Obtain production
app_idfrom CodePay; do not ship the demo id outside dev/test. - Wire
CodepayAdapter+ anEcrTransportthat sendsencode()payloads on the bridge socket and returns raw JSON strings todecode(). - Map
EcrHubResult.normalized+ command context intoTerminalPaymentWebhookDtoand POST toterminal-webhookwithx-pos-terminal-key. - Align
pay_scenariowith store payment methods (card vs QR). - If
PRE_AUTHis required, extendCodepayAdapteror document a dedicated path — constants already exist incodepay-constants.ts. - Add/extend
*.spec.tsfor any new adapter branches per project rules.
Related
Section titled “Related”- POS card terminals & ECR Hub (PAX focus) — bridge pairing, webhook, ECR Hub overview
- POS and payment webhooks
- Frontend applications
Document history
Section titled “Document history”| Version | Date | Notes |
|---|---|---|
| 1.0 | 2026-04-26 | Initial CodePay ECR Hub reference: envelope, topics, adapter, parser, vendor-web lab |