eDDA Direct Debit Deposit
About This Page
What: eDDA authorization lifecycle, eDDI debit execution flow, protocol differences between HSBC and Hang Seng, exception scenarios and error codes Audience: Product managers who need to understand the complete chain behind "user one-click deposit" Prerequisites: Deposit Methods OverviewReading time: 5 minutes Owner: Deposit Product Manager
Key Takeaways: eDDA is authorization, eDDI is the debit instruction — two separate steps enabling "user one-click deposit." HSBC uses OTP SMS + token bucket concurrency; Hang Seng uses digital certificate + pessimistic lock serialization. Protocol differences are the biggest distinction between the two banks.
Quick Navigation — What you might want to do:
- User eDDA authorization failed, what to do? -> Authorization Failure Error Codes + Troubleshooting - Scenario 7
- User eDDA debit rejected, what to do? -> Hang Seng Debit Rejection Error Codes / HSBC Debit Rejection Error Codes
- What's the difference between HSBC and Hang Seng? -> HSBC vs Hang Seng Protocol Differences
- Want to add eDDA for a new bank? -> Change Guide - Adding eDDA
- Can eDDA be used during weekends/maintenance? -> Processing Hours & Maintenance Windows
What are eDDA and eDDI
eDDA (Electronic Direct Debit Authorization) and eDDI (Electronic Direct Debit Instruction) are two separate steps:
| Concept | Full Name | Purpose | Frequency |
|---|---|---|---|
| eDDA | Electronic Direct Debit Authorization | User authorizes moomoo to debit from their bank account | One-time, valid long-term after authorization |
| eDDI | Electronic Direct Debit Instruction | moomoo sends a specific debit instruction to the bank | Once per deposit |
Why separate: Regulatory requirements mandate that banks must obtain explicit authorization from account holders before executing direct debits. eDDA solves "who is allowed to debit," eDDI solves "how much to debit."
Unrelated to withdrawals: eDDA/eDDI is only used for deposit direct debit. HSBC and Hang Seng withdrawal channels use corporate online banking transfers, not eDDI.
PM Perspective
eDDA deposit has the highest conversion rate among deposit methods — users don't need to switch to a banking app, completing everything with one click within moomoo. Understanding every link in this chain is the foundation for optimizing the deposit experience.
Authorization Lifecycle
Authorization State Machine
| Status Code | Constant | Meaning | User Perception |
|---|---|---|---|
| 3 | EDDA_STATUS_WAITING | Waiting — request created, not yet sent to bank | "Authorization processing" |
| 1 | EDDA_STATUS_PENDING | Pending — request sent, awaiting bank response | "Authorization processing" |
| 2 | EDDA_STATUS_EFFECT | Effective — can initiate eDDI debit | "Authorized" |
| 0 | EDDA_STATUS_FAIL | Failed — bank rejected | Shows specific failure reason |
Data Table: setup_eddis, uniquely identified by uid + bank_card_number.
Authorization Polling Mechanism
After the authorization request is sent, the bank doesn't return results immediately. The system polls bank authorization status via SetupEddaCheckAuthorizationJob:
| Parameter | Value | Description |
|---|---|---|
| Polling interval | 600 seconds (10 minutes) | Checks bank-side authorization status once per interval |
| Max retries | ~3,000 times | Stops polling after about 20 days |
| Trigger timing | Enqueued after user submits authorization request | SetupEddaCheckAuthorizationJob::enqueue() |
Configuration Location
- Polling interval: hard-coded in
SetupEddaCheckAuthorizationJobenqueue delay - Max retries: Job framework max_attempts config
- Code path:
deposit/src/app/Business/Job/Deposit/SetupEddaCheckAuthorizationJob.php
Authorization Failure Error Codes
Error codes returned by the bank are stored in the setup_eddis.error_code field. Complete error code mapping:
| Error Code | Failure Reason | User Prompt | Failure Type |
|---|---|---|---|
MFISAC01 | Incorrect account number | Please enter the correct account number | eDDA Authorization |
MPP01005 | Incorrect ID type/number | Please verify ID type and number | eDDA Authorization |
MPP01006 | Phone number doesn't match bank binding | Please enter the correct phone number | eDDA Authorization |
MPP01007 | Account and name don't match | Please verify account and name | eDDA Authorization |
MPP01008 | Bank has no phone number bound | Please contact bank to bind phone number | eDDA Authorization |
MPP01023 | Incorrect phone number bound at bank | Please contact bank to update bound phone | eDDA Authorization |
MPP02003 | Incorrect bank account number | Please re-enter the correct account number | eDDA Authorization |
MPP02011 | Incorrect bank account number | Please re-enter the correct account number | eDDA Authorization |
MPP02013 | Bank has no phone number bound | Please contact bank to bind phone number | eDDA Authorization |
MPP02035 | Incorrect bank account number | Please re-enter the correct account number | eDDA Authorization |
MPP02040 | Incorrect bank account number | Please re-enter the correct account number | eDDA Authorization |
MPP03001 | Bank account type abnormal | Please contact bank | eDDA Authorization |
MPP04000 | Invalid verification code | Please request a new one | eDDA Authorization |
MPP04003 | Incorrect verification code | Please re-enter | eDDA Authorization |
MPP04004 | Verification code expired | Please request a new one | eDDA Authorization |
MPP06001 | Bank account status abnormal | Please contact bank | eDDA Authorization |
ECH09001 | Bank authorization failed (generic) | Please verify info is correct and re-authorize | eDDA Authorization |
Configuration Location
Code path: deposit/src/app/Business/SetupEddi.php:91-113 — errorCodeMessageDict()
Debit Execution Flow
Job Chain
After a user initiates an eDDA deposit, the system executes the debit through an async Job chain:
| Step | Job / Method | Responsibility |
|---|---|---|
| 1 | ApplyFollowJob | Check application status, route to corresponding bank's eDDI creation logic |
| 2 | Eddi::applyHsEddi() / Eddi::applyHsbcEddi() | Get eDDA authorization info, mark Apply as used, create debit record |
| 3 | SBA HsEddi/HsbcEddi.create() | Send debit Procedure creation request to SBA service |
| 4 | HsEddiResultJob / HsbcEddiResultJob | Poll SBA Procedure status, get bank processing result |
| 5 | Credit / Failure handling | Success: write bank statement + SBA executes credit; Failure: update setup_eddis status |
Duplicate Debit Prevention
| Mechanism | Implementation | Protection Scenario |
|---|---|---|
| Apply occupation lock | Apply::setUsed() atomic update | Same application won't be processed by two Jobs simultaneously |
| apply_id unique index | Unique index on hsbc_eddis / hs_eddis tables | Same application only generates one debit record |
| request_id idempotency | SBA deduplicates by request_id | Network retries won't cause duplicate debits |
| SBA create retry | HsbcEddiSBACreateRetryJob / HsEddiSBACreateRetryJob | Retry after 60 seconds if SBA creation fails |
eDDI Deposit Types
eDDI deposits are divided into 6 subtypes, determining whether funds are frozen after deposit:
| Code | Constant | Meaning | Funds Frozen |
|---|---|---|---|
| 1 | FOUNDING_AIP | Fund DCA | No |
| 2 | STOCK_MP | Stock DCA | No |
| 3 | FUND_PURCHASE | Fund purchase | No |
| 11 | FUND_HOLD | Fund DCA + freeze | Yes |
| 21 | STOCK_HOLD | Stock DCA + freeze | Yes |
| 31 | FUND_PURCHASE_HOLD | Fund purchase + freeze | Yes |
Freeze meaning: Deposits with codes 11/21/31 have funds frozen after crediting, reserved for the corresponding investment operations (DCA debit, fund subscription). Freeze release timing is controlled by the investment-side system.
Corresponding DepositType is NORMAL_HOLD = 11 (deposit type reference -> Deposit Quick Reference - Deposit Type).
Configuration Location
Code path: deposit/src/app/Common/EddiDepositType.php
HOLD_LIST = [11, 21, 31]— list of types requiring freezeALL_LIST = [1, 2, 3, 11, 21, 31]— all types
If Requirements Change: eDDA/eDDI Related
| Change | Location | Notes |
|---|---|---|
| Add new eDDI deposit type | EddiDepositType.php + SBA orchestration config | Add enum value, configure whether to freeze |
| Modify debit currency | sba_hsbc_eddi / sba_hase_eddi request parameters | Currently only supports HKD |
| Modify polling interval | sba_hsbc_eddi_worker.ini / sba_hase_eddi config | Adjust rush/normal interval |
| Modify token bucket rate (HSBC) | sba_hsbc_eddi token_bucket daemon | Adjust frequency parameter |
| Replace Hang Seng digital certificate | sba_hase_eddi/conf/ | Replace P12 file and password |
| Add new eDDA supported bank | See Deposit Change Guide - Scenario 8 | Involves commercial + technical + SBA, months-long cycle |
eDDA Supported Scope
Banks Supported via HSBC Channel (15 banks)
eDDA initiated through the HSBC channel supports personal accounts at the following 15 banks:
| Bank | Bank Code | Onboard Date | Notes |
|---|---|---|---|
| HSBC | 004 | Initial | Same-bank debit |
| Hang Seng | 024 | Initial | Cross-bank debit |
| BOCHK | 012 | 2021-06 | Cross-bank debit |
| Nanyang Commercial Bank | 041 | 2021-07 | Cross-bank debit |
| Fusion Bank | — | 2021-09 | Virtual bank |
| ICBC(Asia) | 072 | 2021-09 | Cross-bank debit |
| SCB | 003 | 2021-12 | Cross-bank debit |
| ZA Bank | — | 2021-12 | Virtual bank |
| CNCBI | 018 | 2021-12 | Cross-bank debit, card number requires 15-digit format (pad 0s in first 3 digits) |
| CMB Wing Lung | 238 | 2021-12 | Cross-bank debit |
| Airstar Bank | — | 2022-09 | Virtual bank |
| CCB(Asia) | 009 | 2023-10 | Cross-bank debit |
| LIVI BANK | — | 2024-01 | Virtual bank |
| CMB | — | 2024-03 | Cross-bank debit |
| Dah Sing Bank | 040 | 2024-03 | Cross-bank debit |
Banks Supported via Hang Seng Channel (12 banks)
eDDA initiated through the Hang Seng channel supports personal accounts at the following 12 banks:
Supported by both channels (7 banks) — these banks support both HSBC and Hang Seng channels; HSBC channel is recommended:
| Bank | Bank Code | Notes |
|---|---|---|
| Hang Seng | 024 | Same-bank debit |
| HSBC | 004 | Cross-bank debit |
| BOCHK | 012 | Cross-bank debit |
| Nanyang Commercial Bank | 041 | Cross-bank debit |
| ICBC(Asia) | 072 | Cross-bank debit |
| SCB | 003 | Cross-bank debit |
| CMB Wing Lung | 238 | Cross-bank debit |
Hang Seng channel exclusive (5 banks) — these banks can only use eDDA through the Hang Seng channel, not supported by HSBC:
| Bank | Bank Code | Notes |
|---|---|---|
| Citibank | 250 | Cross-bank debit |
| BEA (Bank of East Asia) | 015 | Cross-bank debit, note "daily single" frequency limit |
| OCBC Wing Hang | — | Cross-bank debit |
| Chiyu Banking | — | Cross-bank debit |
| Bank of Communications HK Branch | 382 | Requires counter application |
Supported ID Types
eDDA authorization requires user ID verification; supported ID types vary by bank:
| ID Type | HSBC Channel | Hang Seng Channel | Notes |
|---|---|---|---|
| HKID (Hong Kong Identity Card) | Supported | Supported | Most common, covers most users |
| PRC Identity Card | Supported | Supported | Mainland account opening users |
| Passport | Supported | Supported | Overseas users |
Unsupported Scenarios
The following scenarios do not support eDDA deposit; PMs and operations should note:
| Scenario | Reason | Suggested User Prompt |
|---|---|---|
| Corporate accounts | eDDA only supports personal accounts; bank side does not allow corporate account authorization | Guide to use online banking transfer or FPS |
| CNCBI card number not 15 digits | CNCBI bank card numbers must be padded to 15 digits (3 leading zeros), otherwise authorization fails | Guide user to confirm correct card number format |
| Airstar Bank exceeds daily limit | Airstar eDDA daily limit HKD 200,000, max 5 transactions per day (total HKD 1,000,000) | Prompt user to split across multiple days or use another channel |
HSBC vs Hang Seng Protocol Differences
| Dimension | HSBC | Hang Seng |
|---|---|---|
| TransType | 303 (HSBC_EDDI) | 302 (HASE_EDDI) |
| Deposit method code | eddaHSBC (code 9) | edda (code 8) |
| Communication protocol | HTTPS REST API | SM2 signed HTTP POST |
| Encryption | TLS (standard HTTPS) | HK Post e-Certificate SM2 digital signature |
| SBA Service | sba_hsbc_eddi | sba_hase_eddi |
| Data Table | hsbc_eddis | hs_eddis |
| Authorization requirement (same-bank) | Must pre-authorize | Must pre-authorize |
| Authorization requirement (cross-bank + online opening) | Allows post-authorization | Not allowed, must pre-authorize |
| Debit currency | HKD only | HKD only |
| Debit result Job | HsbcEddiResultJob | HsEddiResultJob |
| SBA create retry Job | HsbcEddiSBACreateRetryJob | HsEddiSBACreateRetryJob |
Pre-authorization vs Post-authorization
PM Focus: Hang Seng's "must pre-authorize" means new users have a longer first deposit flow (need to wait for authorization to take effect), while HSBC's cross-bank + online opening scenario allows "deposit first, authorize later," lowering the first deposit barrier.
Complete Debit Flow Comparison
The diagram below shows the complete chain differences between HSBC and Hang Seng from user click to fund crediting:
Key Differences:
- Communication protocol: HSBC uses standard HTTPS REST API; Hang Seng uses SM2 signed HTTP POST (national encryption algorithm)
- Data table: HSBC writes to
hsbc_eddis; Hang Seng writes tohs_eddis - Polling Job: HSBC uses
HsbcEddiResultJob; Hang Seng usesHsEddiResultJob - Debit callback time: Both banks have similar processing times (typically minutes), but Hang Seng has a shorter Sunday maintenance window (00:00~08:30)
Hang Seng Debit Rejection Error Codes
Error codes and meanings when Hang Seng eDDI debit fails:
| Error Code | Meaning | User Prompt |
|---|---|---|
BRC_8I1 | Insufficient balance | Bank account has insufficient balance; bank may charge a fee and cancel authorization |
BRC_8RZ | Account abnormal | Bank account abnormal, please contact bank |
BRC_8RW + FP2414 | Authorization not found | Cannot debit, please contact bank |
BRC_8RW + FP2415 | Authorization not effective | Please activate eDDA at bank |
BRC_8RW + FP2416 | Authorization expired | Please re-authorize at bank |
BRC_8RW + FP2417 | Exceeded authorization limit | Please reduce amount and retry |
000 | Generic failure | Please contact bank to confirm reason |
HSBC Debit Rejection Error Codes
| Error Code | Meaning | User Prompt |
|---|---|---|
MPP02020 | Authorization cancelled/does not exist | Please contact bank to re-authorize |
MPP02021 | Payment account closed | Please contact bank |
MPP02022 | Exceeded eDDA debit limit | Please contact bank to adjust limit |
MPP02023 | Authorization cancelled/does not exist | Please contact bank to re-authorize |
MPP02038 | Authorization dormant | Please contact bank to re-authorize |
MPP02039 | Authorization expired | Please contact bank to re-authorize |
MPP05000 | Exceeded debit limit | Please contact bank to adjust limit |
Configuration Location
- Hang Seng error codes:
deposit/src/app/Business/Eddi.php:341-377—hsRejectMessage() - HSBC error codes:
deposit/src/app/Business/Eddi.php:379-397—hsbcRejectMessage()
Processing Hours & Maintenance Windows
| Dimension | Hang Seng | HSBC | Notes |
|---|---|---|---|
| Business hours | Mon 07:00 ~ Sat 10:00 | Mon 07:00 ~ Sat 10:00 | SBA processes eDDI instructions during this window |
| Sunday maintenance | 00:00 ~ 08:30 | 00:00 ~ 12:00 | Bank system maintenance, no requests accepted |
| Off-hours behavior | Instructions enter Blank status, auto-activated next business period | Same | Users won't perceive delay |
| Reconciliation pause | 16:05 ~ 16:10 | 16:05 ~ 16:10 | Consistent with global 2412 rule |
Processing of off-hours submissions: If a user submits an eDDA deposit after Saturday 10:00, the system creates Apply and setup_eddis records, but the SBA eDDI Procedure won't execute immediately. The Procedure enters Blank status, auto-activating Monday 07:00 to send the debit instruction to the bank.
Configuration Location
- Hang Seng maintenance window:
sba_hase_eddiservice constant config - HSBC maintenance window:
sba_hsbc_eddiservice constant config - 2412 pause rule:
deposit/src/app/Business/DepositConfigNew.php:603-641
setup_eddis Complete Status Flow
eDDA deposits involve setup_eddis table status transitions, covering the full process from authorization to deposit completion:
| Status Code | Constant | Meaning | Next Action |
|---|---|---|---|
| 0 | STATUS_PENDING | Pending — record created | applySetupEddi() triggers processing |
| 1 | STATUS_PROCESSING | Processing — debit instruction sent | Await bank callback |
| 2 | STATUS_DEDUCTED | Deducted — bank has debited | SBA executes crediting |
| 3 | STATUS_FAIL | Failed — authorization or debit failed | Notify user, record error_code |
| 4 | STATUS_FINISH | Complete — funds credited | Flow ends |
Special scenario: STATUS_DEDUCTED -> STATUS_FAIL (CRM rejection) — when the bank has completed debit but backend operations terminates the deposit, error_code is REJECT_BY_CRM, requiring manual refund processing.
eDDA Exception Quick Reference
| Exception | Symptom | Troubleshooting Path | Handling |
|---|---|---|---|
| Authorization not effective for long time | User waiting over 24h | Check setup_eddis.edda_status, confirm if still PENDING | Guide user to contact bank for confirmation |
| Authorization failed | User sees error prompt | Check setup_eddis.error_code, reference error code table above | Guide user to correct info per error code |
| Debit failed | Apply status changed to rejected | Check hsbc_eddis / hs_eddis table reject info | Handle per debit error code table |
| Debit succeeded but not credited | Bank debited, balance not increased | Check setup_eddis.status stuck at DEDUCTED(2) | Check SBA Procedure status |
| Debit timeout | No bank callback | Check ResultJob polling status | Wait for polling to complete or manually query bank |
| Authorization invalidated | Previously authorized user suddenly can't deposit | Check bank-side authorization status (may have been cancelled by user at bank) | Guide user to re-authorize |
Detailed troubleshooting flow -> Deposit Troubleshooting - eDDA Authorization Failure
Step-by-Step Runbook: eDDA Debit Succeeded but Not Credited (SBA Procedure Hung)
Symptom: Bank has debited (setup_eddis.status = 2 DEDUCTED), but user balance not increased.
Minute 1 — Locate the blocking point
- Check
setup_eddistable: confirmstatus = 2(Deducted) - Check SBA Procedure status: Running / Pending / Failed?
- If Procedure is Running and over 10 minutes -> abnormal
Minutes 2~5 — Investigate SBA exception 4. Check SBA service logs: is the Procedure stuck at a particular Step? 5. Common stuck points:
- CRM account locked (another operation in progress) -> wait for release
- SBA balance calculation failed -> check account data consistency
- SBA service itself abnormal -> check SBA service status
Minutes 5~15 — Handle 6. If Procedure status is Failed -> need manual re-execution trigger (contact technical support) 7. If Procedure status is Pending -> check if there are preceding Procedures queued 8. If SBA service is abnormal -> Procedure will auto-continue after service recovery
User communication: Inform user "Bank has debited successfully, funds are being processed" to avoid panic
Not resolved after 30 minutes -> Escalate to deposit technical lead
Insufficient Balance Auto-cancels Authorization
Hang Seng Bank has a special behavior: when eDDI debit is rejected due to insufficient balance (BRC_8I1), the bank may automatically cancel that user's eDDA authorization. This means:
- The user needs to top up their bank account first
- Then they also need to re-complete eDDA authorization before using eDDA deposit again
- Operations should proactively inform the user of this, otherwise the user will assume they can continue depositing after topping up
HSBC does not have this behavior — after rejection due to insufficient balance, authorization remains valid and the user can retry directly after topping up.
Handling suggestion: If a user reports "eDDA worked before but doesn't now," first check if it's a Hang Seng user + previously rejected for insufficient balance. If yes -> guide re-authorization.
Common Misconceptions
| Misconception | Fact |
|---|---|
| "eDDA and eDDI are withdrawal channels" | No. eDDA/eDDI is only used for deposit direct debit. HSBC/Hang Seng withdrawals go through corporate online banking transfers, unrelated to eDDA |
| "All banks can use eDDA" | HSBC channel supports 15 banks' personal accounts, Hang Seng channel supports 12 (including 5 exclusive). Corporate accounts not supported. See Supported Scope |
| "eDDA authorization is permanent once completed" | Authorization may be invalidated due to bank-side maintenance, user cancellation in banking app, or auto-cancellation due to insufficient balance (Hang Seng) |
| "eDDA deposit needs the matching engine" | No. eDDA is a system-initiated debit; it inherently knows fund ownership and completely skips the matching engine |
| "HSBC and Hang Seng eDDA implementations are the same" | Protocols are completely different. HSBC uses HTTPS REST API, Hang Seng uses SM2 signing. Data tables, Jobs, and SBA services are all independent |
| "eDDA can be used for deposits on weekends" | Saturday after 10:00 through Sunday is off-hours. Submitted requests queue until Monday 07:00 for execution |
After Reading
| I want to... | Go to |
|---|---|
| Understand how eDDA authorization enables deposit channels | Card Binding & Deposit Authorization - eDDA |
| Walk through a complete eDDA deposit end-to-end | Getting Started Guide - eDDA Deposit |
| See how to handle eDDA authorization/debit failures | Deposit Troubleshooting |
| See operations-side eDDA troubleshooting guide | eDDA Troubleshooting Guide |
| Look up eDDI type codes and status codes | Deposit Quick Reference |
| Dive into HSBC eDDA implementation | HSBC |
| Dive into Hang Seng eDDA implementation | Hang Seng |
| Understand how SBA orchestration executes crediting | SBA Fund Orchestration |