Skip to content

FAQ ​

Updated: 10/30/26, 12:30:54 PM

Answers to the most frequent questions about using, deploying, and extending DaxPay. For anything not covered here, go to GitHub Issues or the community groups.

Basics ​

What is DaxPay ​

DaxPay is an open-source payment system that provides core payment capabilities — pay, refund, query, callback, and more — and wraps multiple payment channels such as Alipay, WeChat, and UnionPay behind a unified HTTP interface, so a business system integrates once with a single standard protocol and gains access to multiple pay methods. See Introduction.

What is the difference between the open-source and commercial editions ​

The open-source edition (this project) delivers the complete core payment pipeline and multi-client admin UIs; the payment platform core is open-sourced under LGPL-3.0-or-later.

The commercial edition (dax-pay-plus) extends it:

  • More payment channels — the open-source edition ships direct channels built in; the commercial edition adds aggregate and international channels plus onboarding assistance
  • Settlement center, enhanced risk control, and other enhanced capabilities — see the official pricing page for the version differences

Transfer and allocation are already included in the open-source edition.

Can LGPL v3.0 be used commercially ​

Yes. LGPL-3.0-or-later allows commercial use as long as you meet the license conditions. Your business system usually calls DaxPay over HTTP APIs and need not disclose your business code for that; modifying or distributing DaxPay itself, or using other components, still requires complying with the corresponding terms. See License.

Deployment and Running ​

Is mvnd required to compile the backend ​

The project convention is mvnd (Maven Daemon) to speed up builds. Plain mvn also compiles, but mvnd keeps a daemon that makes incremental builds significantly faster — it is the recommended option.

Can Apache Artemis be replaced with another message queue ​

The current version has a hard dependency on Artemis (for JMS scenarios such as delayed payment notifications); direct replacement by RabbitMQ / Kafka is not supported. Replacing it means reworking the messaging middleware adaptation layer yourself.

Delayed messages are delivered early or late ​

Symptom: a delayed message set to 10 seconds is consumed after about 6; or one set to 5 seconds arrives only after 8. Cause: the app and the Artemis broker are deployed on different machines with inconsistent clocks; delayed messages are delivered by absolute timestamp, so the actual delay shifts by the clock difference. Fix: enable NTP time sync on both machines. See Delayed Message Clock Skew.

Behind a CDN, login fails with "request verification expired" or "request timestamp expired" ​

Symptom: everything works when hitting the backend directly, but behind a CDN (Tencent EdgeOne, Cloudflare, etc.) login always fails with "request verification expired, please retry" or "request timestamp expired, please retry".

Cause: the login anti-replay mechanism fetches a one-time nonce before each login; once the CDN caches this GET endpoint's response, every user gets the same stale nonce — within the 5-minute cache window the first error appears (the nonce was already consumed), past 5 minutes the second (the timestamp is outside the tolerance). A cached image-captcha endpoint behaves the same way (everyone sees the same image).

Diagnosis: request an endpoint that is still a GET (e.g. the public key) twice in a row and watch the cache status:

bash
curl -sD - -o /dev/null https://your-domain.com/api/token/public-key | grep -i "cache-status"
# EO-Cache-Status: HIT means a cache hit (Tencent EdgeOne; for Cloudflare check CF-Cache-Status)

Fix: configure no-cache rules in the CDN for all API paths (/api/* plus /unipay/* and /client/* on the gateway domain), see CDN Deployment Notes.

Behind a CDN, in-app notifications keep disconnecting and reconnecting ​

Symptom: the browser console repeatedly reports a connection-interrupted error such as ERR_HTTP2_PROTOCOL_ERROR 200 (OK); notification messages only appear after a page refresh; the request drops and reconnects at fixed intervals.

Cause: in-app notifications use an SSE long-lived connection (/api/notify/user/sse/connect) kept alive by periodic server heartbeats; when any layer in the middle (CDN origin timeout, proxy timeout) gives up waiting for data earlier than the heartbeat interval, it cuts the stream after the browser has already received the 200, and the frontend reconnects automatically — a repeating disconnect loop. This is a long-connection timeout problem, not a caching problem, and not an auth or backend error.

Fix: configure an origin timeout ≥ 60 seconds for /api/notify/* on the CDN (it must exceed the heartbeat interval, currently 10 seconds); on EdgeOne, the rule only takes effect in the environment the site actually resolves to ("Mainland China" vs "Global"). See In-App Notifications Disconnect and Reconnect.

Uploads fail with a cross-origin (CORS) error ​

Symptom: "Test Connection" passes, but uploading files reports a cross-origin error (No 'Access-Control-Allow-Origin' header ...) or simply fails. Cause: presigned direct upload is a cross-origin request from the browser straight to the object storage, and the bucket does not allow it; the backend "Test Connection" never goes through a browser and cannot detect this. Fix: configure CORS rules on the bucket — origin set to the admin UI and H5 origins (* is fine while testing), methods must include PUT, add ETag to the expose headers; on Cloudflare R2, * is only valid in AllowedOrigins. See Storage CORS Configuration and Upload Troubleshooting.

Voice broadcast activated but no announcement received ​

Symptom: the voice broadcast APP activated successfully, but the phone does not announce or print on payment success. Cause: broadcast messages travel over a persistent MQTT over WebSocket (wss) connection; an unset-up or inconsistent pipeline — empty platform "MQTT Access" configuration, a wrong access URL, certificate problems, or the access domain behind a CDN that does not support long connections — all look the same, and the APP shows no clear error. Fix: check link by link from the platform side per Voice Broadcast Not Received; for the full setup see Voice Broadcast (Virtual Device).

Which databases are supported ​

Only PostgreSQL 14+. Time fields uniformly use timestamptz(6) and entity classes use OffsetDateTime; MySQL / Oracle and the like are not supported.

Minimum specs for containerized deployment ​

Minimum recommended: 2 cores / 4 GB RAM (with PostgreSQL / Redis / Artemis / the main app / channel sub-apps all containerized). For production, start from 4 cores / 8 GB and scale the channel sub-app instances horizontally with transaction volume.

mvnd build arguments fail on PowerShell ​

On PowerShell, -D arguments containing = get split on spaces — they must be quoted:

powershell
# Wrong — the argument gets split
mvnd compile -Dmaven.test.skip=true

# Correct — quoted
mvnd compile "-Dmaven.test.skip=true"

See Build.

One-Click Deployment and the Operations Panel ​

Frequent questions when using one-click deployment or the operations panel; for the full staged troubleshooting steps, see Troubleshooting in the operations panel documentation.

Online install fails to download or fails the checksum ​

The download source may be down or install.sh outdated — switch the source and retry, or use offline installation instead.

Offline install does nothing and prompts to download online ​

install.sh and the .xz installer package must be in the same directory (the script only probes the directory it lives in, not $PWD), and it must be run with bash install.sh.

Forgot the panel's initial password, or the login is locked ​

daxpay info shows the initial account and password; if you changed it and lost it, reset with daxpay reset-password; after 5 consecutive failures, lift the lockout with daxpay unlock.

Port conflicts (8060 / 80) ​

Middleware and business services do not map ports by default; the host occupies only these two ports (panel 8060, gateway 80). Stop the occupying process, or change the panel port with --port. HTTPS is terminated by the outer CDN / gateway; the origin gateway no longer listens on 443.

Business backend container unhealthy and restarting in a loop ​

A common root cause is an empty database password, or a password that differs between the business side and the middleware, so authentication fails. Confirm the password is non-empty via "View Passwords" on the base environment service card, realign it with "Change Password" in the card's "Config" drawer, then restart the business containers.

A drift banner appears on the page ​

A "Redeploy <service>" banner means that service's port/memory/exposure-mode change has not taken effect — clicking rebuilds only that container; a whole-stack "Redeploy" means stack-level changes (version/JVM/memory tier) have not taken effect and all enabled containers must be rebuilt; "Apply gateway configuration" means gateway configuration changes are unapplied — click to apply (nginx reload, no container rebuild needed).

Deployed, yet the panel shows "not deployed" ​

The "deployed" state is determined by runtime container probing plus a deployment snapshot. Containers started manually with docker compose are self-healed into the snapshot when the panel starts; if a deployment is still unrecognized, click "Deploy" once on the corresponding page to let the panel take over.

Payment Channel Integration ​

Which payment channels does the open-source edition support ​

It ships 20+ payment channel capabilities:

  • Direct channels (channel-one) — Alipay, WeChat Pay, Douyin Pay, UMS, UnionPay (Cloud QuickPass)
  • Aggregate and international channels (channel-two / channel-three) — Lakala, HKRT, Dougong (Huifu), Leshua, VBILL (Suixingfu), HMPay (Sand), Adapay, Fuiou, Stripe, etc., provided on demand through the corresponding extension packages and versions

Direct channels, channel extension packages, and the exact available list follow the version notes and the official product pages.

See Features - Channel Count and Distribution.

How to add a payment channel ​

  1. Under the daxpay-channel-impl module of the corresponding channel sub-app (direct channels go to channel-one, aggregate to channel-two), implement the SDK calls and the signing/verification
  2. In the main app's daxpay-channel, register the channel strategy and the configuration data CRUD
  3. Add the channel to ChannelEnum

Adding a channel only means implementing the SDK calls and registering the strategy — the main payment path is unaffected.

What is the difference between channel-one / channel-two / channel-three ​

The three share an identical architecture; they differ in which set of channels they carry:

  • channel-one — direct channels such as Alipay and WeChat (official SDKs)
  • channel-two — aggregate channels such as Lakala and Fuiou (aggregate SDKs)
  • channel-three — international channels such as Stripe

The main app routes them via daxpay.channel.one/two/three.base-url; the sub-app numbering is fixed and must not be renamed.

Java or Go for the channel sub-apps ​

channel-one ships two fully equivalent implementations, Java (Spring Boot) and Go (Gin), with identical ports, routes, and response contracts:

  • Java — complete ecosystem, official channel SDKs off the shelf, best for fast integration
  • Go — higher throughput and lower memory footprint, with all HTTP signing self-implemented (no third-party SDKs)

Choose by team stack and performance needs — pick one of the two; they conflict on the same port and must not run simultaneously.

Must the channel sub-apps be deployed independently ​

Yes. Independent deployment of the channel sub-apps is DaxPay's core design — third-party SDKs are isolated inside the sub-apps, keeping SDK dependencies out of the main app and enabling independent upgrades and elastic scaling. The main app calls the sub-apps through declarative @HttpExchange HTTP clients, with AES-GCM encryption on the path.

WeChat service-provider payment fails with "受理机构必须传入sub_mch_id" ​

Symptom: in service-provider mode, initiating a payment fails with the WeChat error "受理机构必须传入sub_mch_id", even though the request body clearly carries a sub_mchid value.

Cause: the wording is misleading — it actually means WeChat rejected the "service provider ↔ sub-merchant" binding check, not that a field is missing. The most common trigger is a configuration mistake: the provider's own merchant number was filled into the sub-merchant field (sp_mchid and sub_mchid carry the same number), and WeChat does not allow a provider to sub-contract itself.

Diagnosis: check the request body in the channel sub-app logs — sp_mchid == sub_mchid (and sp_appid == sub_appid) confirms a configuration problem.

Fix: if the merchant is an ordinary merchant (no service-provider qualification), it should not use the WeChat service-provider product — switch to WeChat direct mode; if it really is a provider, the sub-merchant field must carry the actual secondary merchant number bound during onboarding. See WeChat Service-Provider Payment Error for the full walkthrough, and WeChat Pay for channel configuration.

Feature Boundaries ​

Are allocation and withdrawal supported ​

Allocation is supported: direct-merchant allocation over the Alipay, WeChat, and Douyin channels. Flow: create the payment order with allocation=true so it becomes allocatable → initiate allocation / query / sync through the open APIs; allocation receivers must be bound on the channel side in advance (binding management is available in the admin console under "Channel Merchant Details → Allocation Receivers"). See the API documentation.

Withdrawal (payout) is not implemented yet; it is a future enhancement.

Is international payment supported (PayPal / Stripe) ​

Stripe is integrated through the channel-three sub-app (continuously improving) and supports international card-scheme payments. PayPal and other international channels are on the roadmap.

How does the sandbox environment work ​

DaxPay uses deployment-level sandbox isolation:

  • Test / development environments configure daxpay.platform.config.sandbox-enabled=true to allow sandbox testing
  • Production environments must configure sandbox-enabled=false; on startup, every product with activeEnv=sandbox is forcibly reset to prod, keeping production data clean

Production isolation

Do sandbox testing on a separate test deployment; never import data from a test environment into the production database. See Configuration.

Is multi-merchant support / multi-merchant isolation available ​

Yes. DaxPay supports multi-merchant mode natively:

  • Admin + merchant dual portals with row-level data isolation (automatic, keyed by merchant number)
  • A single merchant can configure multiple payment apps with independent credentials and callback URLs

Secondary Development ​

Can I modify the source code for my own project ​

Yes. Under LGPL-3.0-or-later you may modify and use the source code per the license. If you modify DaxPay source code covered by the license (rather than calling its APIs), you must provide the modified source under LGPL terms; if you only call the HTTP APIs, your business code usually need not be open-sourced for this.

How do frontends call the backend APIs ​

Frontends call the main app (port 9999) over RESTful HTTP; API requests and responses support RSA signatures against tampering, and authentication is based on Sa-Token (token name: Accesstoken). See the API documentation for details.

How to add a UI language (i18n) ​

  • Backend — resource files live in common-i18n/src/main/resources/i18n/{locale}/, split by business module; adding a language means maintaining zh-CN / en-US / zh-TW / zh-HK / ja-JP / ko-KR together
  • Frontend (Web/H5) — 10 languages; new keys must be added to all of them
  • Mini-programs — Chinese and English only (conditional compilation strips Traditional Chinese / Japanese-Korean / ASEAN packs on mini-program targets)

See each client's i18n guide for details.

Where are the database table schemas, and how to initialize them ​

The SQL scripts live in the _config/sql/ directory of the main app repository. A fresh install runs table.sql (create tables) → data.sql (initial data, sanitized). The repository also provides the upgrade scripts update-tables.sql → update-datas.sql and the menu seed iam_perm_menu.sql. See Build - Database Preparation.

Why can the cache end up "L1 read-only, no Redis reads or writes" ​

This is not a normal form of the ordinary cache but a safety downgrade for sensitive caches: when a cache name hits the sensitive list (secure: prefix or secureNames config) and platform data encryption is disabled, sensitive data is forbidden to be written to Redis in plaintext — writes go to the local L1 memory only, and reads fall straight through. Once data encryption is enabled, the L1+L2 two-level behavior is restored automatically. See the full behavior matrix in Cache L1 Read-Only Downgrade.


Didn't find your answer? Feel free to ask on GitHub Issues or join the community groups to discuss.