Skip to content

POS card terminal — CodePay (ECR Hub)

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).


ConcernDetail
ChannelWebSocket — 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.
HeartbeatClients may send bridge_heartbeat so the server keeps store_devices.is_connected accurate; the CodePay lab does this while testing.
Outcome to SaaSProduction 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).


Each outbound request is a JSON object with (at minimum):

FieldRole
versionProtocol version — constants CODEPAY_ECR_HUB_VERSION_1_1 ("1.1") or CODEPAY_ECR_HUB_VERSION_2_0 ("2.0").
app_idMerchant / 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.
topicECR Hub topic (see table below).
request_idCorrelates request/response; the hub uses the command’s correlationId.
biz_dataTopic-specific payload (e.g. trans_type, amounts, merchant_order_no).

Encoder implementation: packages/ecr-hub/src/manufacturers/codepay/codepay-encoder.ts.


Constant (code)topic valueTypical use
CODEPAY_ECR_HUB_TOPIC_PAYecrhub.pay.orderSale, refund, void (via trans_type in biz_data).
CODEPAY_ECR_HUB_TOPIC_TIP_ADJUSTMENTecrhub.pay.tip.adjustmentPost-auth tip adjustment.
CODEPAY_ECR_HUB_TOPIC_BATCH_CLOSEecrhub.pay.batch.closeBatch close / settlement housekeeping.
CODEPAY_ECR_HUB_TOPIC_QUERYecrhub.pay.queryQuery / report-style lookup (REPORT in the adapter maps here).
CODEPAY_ECR_HUB_TOPIC_REPRINTecrhub.pay.reprintReprint last receipt (PRINTER in the adapter).

String codes align with the official demo’s invoke constants:

ConstantValueMeaning
CODEPAY_TRANS_TYPE_PURCHASE1Purchase / sale.
CODEPAY_TRANS_TYPE_VOID2Void.
CODEPAY_TRANS_TYPE_REFUND3Refund / return.
CODEPAY_TRANS_TYPE_PRE_AUTH4Pre-authorization (constants exist; CodepayAdapter does not expose PRE_AUTH today — lab samples may build raw envelopes).
CODEPAY_TRANS_TYPE_PRE_AUTH_COMPLETE6Complete pre-auth.
CODEPAY_TRANS_TYPE_QUERY21Query.
CODEPAY_TRANS_TYPE_REPRINT22Reprint.
CODEPAY_TRANS_TYPE_BATCH_CLOSE23Batch close.
CODEPAY_TRANS_TYPE_TIP_ADJUSTMENT24Tip adjustment.

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.


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:

  1. Legacy ECR Hub — top-level { version, app_id, topic, request_id, biz_data } with no top-level response_code / response_msg. Approval follows biz_data.trans_status === "1" only (see constants below).
  2. App-mode (e.g. callAppMode) — top-level response_code, response_msg (or response_message), plus biz_data. Live terminals often return response_code: "0" and response_msg: "SUCCESS" together with biz_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-like response_msg (SUCCESS / APPROVED / OK) or terminal-complete trans_status of "1" or "2". trans_status 3 / 4 still mean cancelled / pending and are never approved.
ValueConstantLegacy biz_data-only envelopeApp-mode + host fields
1CODEPAY_TRANS_STATUS_SUCCESSApprovedCompleted / success
2CODEPAY_TRANS_STATUS_FAILDeclined / failed (legacy)Often completed sale when paired with response_code: "0" and response_msg: "SUCCESS"
3CODEPAY_TRANS_STATUS_CANCELCancelledCancelled
4CODEPAY_TRANS_STATUS_PENDINGPendingPending

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.actionMaps to
SALEecrhub.pay.order + purchase trans_type; optional metadata onScreenTip, onScreenSignature, description.
RETURNecrhub.pay.order + refund; requires reference as original merchant_order_no; optional tipAmountCents.
VOID_SALE / VOID_RETURNecrhub.pay.order + void; requires reference as orig_merchant_order_no.
TIP_ADJUSTMENTecrhub.pay.tip.adjustment.
BATCH_CLOSEecrhub.pay.batch.close.
REPORTecrhub.pay.query (uses merchant_order_no from reference / correlationId).
PRINTERecrhub.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.


AreaLocation
Device recordVendor → 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 payloadsapps/vendor-web/src/lib/codepay-ws-lab-samples.ts — includes pre-auth style samples for manual testing (may not match CodepayAdapter surface).

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_id from CodePay; do not ship the demo id outside dev/test.
  • Wire CodepayAdapter + an EcrTransport that sends encode() payloads on the bridge socket and returns raw JSON strings to decode().
  • Map EcrHubResult.normalized + command context into TerminalPaymentWebhookDto and POST to terminal-webhook with x-pos-terminal-key.
  • Align pay_scenario with store payment methods (card vs QR).
  • If PRE_AUTH is required, extend CodepayAdapter or document a dedicated path — constants already exist in codepay-constants.ts.
  • Add/extend *.spec.ts for any new adapter branches per project rules.


VersionDateNotes
1.02026-04-26Initial CodePay ECR Hub reference: envelope, topics, adapter, parser, vendor-web lab