Visii Changes
Branch: fea-260301-iban-migrateI. Change Overview
Visii Changes)) MQ Message Listeners KycFinishListener
KYC completion auto account opening KycDeleteListener
KYC deletion account closure Dubbo Interfaces retryOpenAccount
Counter retry account opening CGS Interfaces
Mini-program API VamAuthInit
Initialization query VamAuthActive
Activation account opening VamAuthInfoQuery
Account info query VamAuthInfoQueryV2
V2 version query Core Services ZandVaServiceImpl
ZAND account opening core logic VaConverter/VaBuilder
VA entity construction Infrastructure CmsClient
Whitelist service MemberClient
Added isVip/grcKycType RetryApplyVamHandler
Compensation retry
II. Use Case Diagram
(App)"] Counter["Counter Personnel
(Operations Portal)"] MQ["MQ Messages
(KYC System Push)"] end subgraph UseCases["Use Cases"] UC1["UC1: KYC Completion Auto Account Opening"] UC2["UC2: KYC Deletion Account Closure"] UC3["UC3: Query IBAN Account Opening Status"] UC4["UC4: Activate Account Opening"] UC5["UC5: Query IBAN Account Information"] UC6["UC6: Counter Batch Retry Account Opening"] end MQ --> UC1 MQ --> UC2 User --> UC3 User --> UC4 User --> UC5 Counter --> UC6 UC1 -. "When account opening fails" .-> UC6 UC4 -. "Depends on" .-> UC3
III. Detailed Module Descriptions
3.1 KYC Completion Auto Account Opening (KycFinishListener)
Change Description
| Item | Description |
|---|---|
| Class Name | KycFinishListener |
| MQ Exchange | exchange.kyc.finish |
| Route Key | kyc.eid.finish / kyc.eid.finish.vip |
| Queue | kyc.visii.queue |
| Trigger Condition | KYC system pushes MQ message after KYC completion |
| Core Logic | Receive message → Check switch/whitelist → Check if VA exists → Build VA → Async call ZAND account opening |
Flowchart
or whitelist hit?} E -- No --> Z2[Skip: Auto account opening not enabled] E -- Yes --> F{VA record exists?} F -- Yes --> Z3[Skip: Idempotent] F -- No --> G[VaBuilder build VA entity] G --> H[Transaction: Save VA + Save compensation event] H --> I[Async execute: Call ZAND account opening API] I --> J[Complete] style Z1 fill:#f9f,stroke:#333 style Z2 fill:#ff9,stroke:#333 style Z3 fill:#9ff,stroke:#333 style J fill:#9f9,stroke:#333
Configuration Items
| Config Key | Type | Default | Description |
|---|---|---|---|
visii.config.zand.customerAutoOpen | String | Y | Auto account opening master switch |
CMS Whitelist VISII_KYC_MID_WHITELIST | - | - | Whitelist hit also allows account opening |
Test Points
| Test Scenario | Input | Expected | Corresponding Test Case |
|---|---|---|---|
| Empty message | null / "" | No exception thrown, skip | KycFinishListenerTest#testHandle_emptyMessage |
| Switch off | customerAutoOpen=N, memberId not in whitelist | Skip | testHandle_switchOff |
| VA exists | Same memberId sent twice | Idempotent, skip second time | testHandle_vaExists |
| Normal flow | Valid KYC message | VA record created, async account opening triggered | testHandle_normalFlow |
3.2 KYC Deletion Account Closure (KycDeleteListener)
Change Description
| Item | Description |
|---|---|
| Class Name | KycDeleteListener |
| MQ Exchange | exchange.kyc.delete |
| Route Key | kyc.delete.eid |
| Queue | kyc.delete.visii.queue |
| Core Logic | Receive delete message → Trigger MemberLogoutEventHandler to close VA |
Flowchart
parameters: memberId, deleteMethod] E --> F[Save compensation event] F --> G[Async execute: Close VA] G --> H[Complete] style Z1 fill:#f9f,stroke:#333 style H fill:#9f9,stroke:#333
Message Example
{
"memberId": "100021922209",
"deleteMethod": "EID_BASIS_MANUAL_DELETE",
"kycType": "EID"
}Test Points
| Test Scenario | Input | Expected | Corresponding Test Case |
|---|---|---|---|
| Empty message | null / "" | No exception thrown, skip | KycDeleteListenerTest#testHandle_emptyMessage |
| memberId empty | {"deleteMethod":"X"} | Skip | testHandle_emptyMemberId |
| No VA record | Non-existent memberId | No error, save close event | testHandle_noVaExists |
| Normal closure | Valid memberId | Trigger close VA | testHandle_normalFlow |
3.3 Counter Batch Retry Account Opening (retryOpenAccount)
Change Description
| Item | Description |
|---|---|
| Interface | CounterOperateFacade#retryOpenAccount |
| Input Parameter | RetryOpenAccountRequest(bankCode, memberIdList) |
| Output Parameter | CommonResponse |
| Core Logic | Iterate memberIdList → Query/create VA → Trigger compensation retry |
Flowchart
bankCode: ZAND/PAYBY
memberIdList: non-empty] B -- Validation failed --> Z1[Return FAIL] B -- Validation passed --> C[Iterate memberIdList] C --> D{Query VA record} D -- VA exists and VALID --> E1[Skip: Already opened] D -- VA exists and CLOSED --> E2[Skip: Already closed] D -- VA exists and not VALID/CLOSED --> F[Trigger RetryApplyVamHandler] D -- VA does not exist --> G[vaConverter.initVa create VA] G --> G1{initVa succeeded?} G1 -- No --> E3[Skip: Cannot create] G1 -- Yes --> G2[insert VA record] G2 --> F F --> H[Save compensation event + async execute] H --> C E1 --> C E2 --> C E3 --> C C -- Iteration complete --> I[Return SUCCESS] style Z1 fill:#f66,stroke:#333 style I fill:#9f9,stroke:#333
Test Points
| Test Scenario | Input | Expected | Corresponding Test Case |
|---|---|---|---|
| bankCode empty | bankCode=null | FAIL | CounterOperateFacadeImplTest#testRetryOpenAccount_validateFail |
| memberIdList empty | memberIdList=null | FAIL | Same as above |
| memberIdList empty list | memberIdList=[] | FAIL | Same as above |
| member has no VA | Random memberId | SUCCESS, create VA and retry | testRetryOpenAccount_noVaExists |
| Normal retry | memberId with existing VA | SUCCESS | testRetryOpenAccount_normalFlow |
3.4 Mini-program CGS Interfaces (VamAuth Series)
Interface List
| Provider | ServiceCode | Method | Description |
|---|---|---|---|
VamAuthInitProvider | /visii/vam/v1/auth/init | Query account opening status | Return openStatus + vipType |
VamAuthActiveProvider | /visii/vam/v1/auth/active | Activate account opening | Trigger ZAND account opening flow |
VamAuthInfoQueryProvider | /visii/vam/v1/auth/query-info | Query account information | Return IBAN/bankName etc. |
VamAuthInfoQueryV2Provider | /visii/vam/v2/auth/query-info | V2 query | Same as above, newVersion=true |
VamAuthInit Flowchart
VamAuthActive Flowchart
async account opening] H -- No --> J I --> J["Return openStatus=VA status"]
VamAuthInfoQuery Flowchart
set vamLimit] I -- No --> K[Return Response] J --> K
Test Points
| Test Scenario | Interface | Expected | Corresponding Test Case |
|---|---|---|---|
| memberId empty | Init/Active/Query | FAIL | VamAuthProcessorTest#testVamAuthInit_memberIdNull etc. |
| No VA record query Init | Init | openStatus=I, vipType has value | testVamAuthInit_noVa |
| VA record exists query Init | Init | openStatus=VA actual status | testVamAuthInit_withVa |
| New member activation | Active | SUCCESS, DB creates VA | testVamAuthActive_newMember |
| Activation with name | Active | SUCCESS | testVamAuthActive_withName |
| Duplicate activation | Active | Idempotent, SUCCESS | testVamAuthActive_idempotent |
| No VA query Info | Query | SUCCESS, bankCode=null | testVamAuthInfoQuery_noVa |
| VA exists query Info | Query | bankCode=ZAND | testVamAuthInfoQuery_withVa |
| V2 version query | QueryV2 | bankCode=ZAND | testVamAuthInfoQuery_v2 |
IV. New/Modified File List
4.1 New Files
| File | Module | Description |
|---|---|---|
KycFinishInfo.java | core/domain/dto | KYC completion MQ message DTO |
KycDeleteInfo.java | core/domain/dto | KYC deletion MQ message DTO |
KycFinishListener.java | domainservice/mq | KYC completion MQ listener |
KycDeleteListener.java | domainservice/mq | KYC deletion MQ listener |
VaBuilder.java | domainservice/builder | Build VA entity from KycFinishInfo |
RetryOpenAccountRequest.java | facade/domain/request | Retry account opening request DTO |
RetryOpenAccountProcessor.java | domainservice/processor | Retry account opening business processor |
VamAuthInitProcessor.java | domainservice/service/impl/va | Init interface processor |
VamAuthActiveProcessor.java | domainservice/service/impl/va | Active interface processor |
VamAuthInfoQueryProcessor.java | domainservice/service/impl/va | InfoQuery interface processor |
VamAuthInitProvider.java | ext/service/provider | CGS Init interface Provider |
VamAuthActiveProvider.java | ext/service/provider | CGS Active interface Provider |
VamAuthInfoQueryProvider.java | ext/service/provider | CGS InfoQuery interface Provider |
VamAuthInfoQueryV2Provider.java | ext/service/provider | CGS InfoQuery V2 Provider |
CmsClient.java / CmsClientImpl.java | ext/integration/internal/cms | CMS whitelist service client |
BaseConverter.java | domainservice/converter | CGS request basic info conversion |
4.2 Modified Files
| File | Changes |
|---|---|
VisiiConstants.java | Added Kyc/KycDelete/ServiceCode constant interfaces |
ZandConfig.java | Removed customerOpenPercent (changed to CMS whitelist control) |
MemberClient.java | Added isVip(memberId) / grcKycType(memberId) |
MemberClientImpl.java | Implemented isVip/grcKycType, renamed private method isVip→isVipKycType |
CounterOperateFacade.java | Added retryOpenAccount method |
CounterOperateFacadeImpl.java | Injected RetryOpenAccountProcessor and delegated |
ZandVaServiceImpl.java | Fully implemented vamAuthInit/vamAuthActive/vamAuthInfoQuery |
VaConverter.java | Added initVa(memberId) method |
4.3 New Test Files
| File | Description |
|---|---|
KycFinishListenerTest.java | KYC completion listener test (4 test cases) |
KycDeleteListenerTest.java | KYC deletion listener test (4 test cases) |
CounterOperateFacadeImplTest.java | Counter interface test (includes retryOpenAccount 5 test cases) |
VamAuthProcessorTest.java | CGS interface processor test (11 test cases) |
V. End-to-End Test Flow
VI. Key Verification Point Checklist
MQ Message Listening
- [ ]
exchange.kyc.finishmessages can be consumed normally - [ ]
exchange.kyc.deletemessages can be consumed normally - [ ] Empty messages/malformed messages do not cause exceptions
- [ ] When
customerAutoOpen=Nand not in whitelist, KYC messages are skipped - [ ] When
customerAutoOpen=Y, normal account opening occurs - [ ] When whitelist hit, account opening works even if switch is off
- [ ] Duplicate MQ messages are handled idempotently (no duplicate VA creation)
Counter Interface
- [ ]
retryOpenAccountparameter validation works (bankCode required and must be ZAND/PAYBY, memberIdList non-empty) - [ ] Already opened (VALID) members are skipped
- [ ] Already closed (CLOSED) members are skipped
- [ ] Members without VA records automatically create VA and retry
- [ ] Partial failures in batch processing do not affect overall operation
CGS Interface (Provided to app)
- [ ]
/visii/vam/v1/auth/initreturns openStatus=I when no VA - [ ]
/visii/vam/v1/auth/initreturns vipType=VIP for VIP users - [ ]
/visii/vam/v1/auth/activetriggers account opening for new users - [ ]
/visii/vam/v1/auth/activeis idempotent on duplicate calls - [ ]
/visii/vam/v1/auth/activehandles name parameter normally - [ ]
/visii/vam/v1/auth/query-inforeturns empty Response when no VA - [ ]
/visii/vam/v1/auth/query-inforeturns bankCode=ZAND when VA exists - [ ]
/visii/vam/v2/auth/query-inforeturns with vamLimit limit information - [ ] Unauthenticated users (no token) are blocked by CGS gateway
Database Verification
- [ ] t_va table new record fields are complete (memberId, bankCode, vaId, status, iban, name, extension etc.)
- [ ] VA status transition correct: INIT → PROCESSING → VALID / FAIL
- [ ] Status after closure: CLOSED
VII. Configuration Change Description
| Change Type | Configuration Item | Description |
|---|---|---|
| Removed | visii.config.zand.customerOpenPercent | No longer use percentage control, changed to CMS whitelist |
| Retained | visii.config.zand.customerAutoOpen | Master switch, default Y |
| Added | CMS Whitelist VISII_KYC_MID_WHITELIST | Used with autoOpen, whitelist hit can bypass switch |
VIII. Transaction Status Change MQ Notification
8.1 Change Description
| Item | Description |
|---|---|
| Exchange | exchange.visii.trans |
| RoutingKey Format | status.${statusCode}.${memberId} |
| Trigger Timing | Transaction first insert (WV), each status change (WV→V/MC, general transition, P→S) |
| Failure Strategy | try-catch wrapped, failure only log.warn, does not affect main flow |
8.2 Message Body TransStatusChangeMessage
| Field | Type | Description |
|---|---|---|
| orderId | Long | Order ID |
| memberId | String | Member ID |
| bankCode | String | Bank code |
| status | String | Current status code |
| bankOrderNo | String | Bank order number |
| previousStatus | String | Previous status code (null for first insert) |
| amount | BigDecimal | Transaction amount |
| currency | String | Currency |
| iban | String | IBAN |
| direction | String | Direction (I/O) |
| gmtModified | LocalDateTime | Modification time |
8.3 Publishing Trigger Points
| Trigger Point | Class | Method | Status Change |
|---|---|---|---|
| First receive transaction notification | NotifyTransProcessor | saveAndValidate4Bank | null → WV |
| Post-validation status update | VamOrderRepositoryImpl | updateStatus(VamOrder, VamOrderStatusEnum, VisiiUnityResultCodeEnum) | WV → V/MC |
| General status transition | VamOrderRepositoryImpl | updateStatus(VamOrder, DbResultCarrier) | Various status transitions |
| Deposit success | VamOrderRepositoryImpl | updateToSuccess(VamOrder, DepositInfo) | P → S |
8.4 Flowchart
insert order] B --> C["Publish MQ: status=WV, prev=null"] C --> D{Validation result} D -- Passed --> E[updateStatus WV→V] D -- Manual needed --> F[updateStatus WV→MC] E --> G["Publish MQ: status=V, prev=WV"] F --> H["Publish MQ: status=MC, prev=WV"] G --> I[Subsequent deposit flow] I --> J[updateToSuccess P→S] J --> K["Publish MQ: status=S, prev=P"] style C fill:#e6f3ff style G fill:#e6f3ff style H fill:#e6f3ff style K fill:#e6f3ff
8.5 Test Points
| Test Scenario | Expected | Corresponding Test Case |
|---|---|---|
| First insert status (previousStatus=null) | No exception thrown, MQ message sent | TransStatusMqPublisherTest#testPublish_initialStatus |
| Status change WV→V | No exception thrown, routingKey=status.V.{memberId} | testPublish_statusChange |
| Status change P→S | No exception thrown, routingKey=status.S.{memberId} | testPublish_pendingToSuccess |
8.6 New/Modified Files
| File | Operation | Description |
|---|---|---|
VisiiConstants.java | Modified | Added TransNotify constant interface |
TransStatusChangeMessage.java | Created | MQ message body DTO |
TransStatusMqPublisher.java | Created | MQ publisher component |
VamOrderRepositoryImpl.java | Modified | 3 update methods call publisher |
NotifyTransProcessor.java | Modified | Call publisher after insert |
TransStatusMqPublisherTest.java | Created | Publisher test cases |
Vis Changes
1. Feature Overview
This change involves the following modules for IBAN migration functionality:
| Module | Feature | Description |
|---|---|---|
| Automatic retry scheduling | Feature 7 | Failed records auto retry (max 3 times, incremental delay) |
| Manual status modification | Feature 8.1 | Counter manually set migration record status |
| Manual retry | Feature 8.2 | Counter manually retry failed records |
| Transaction notification consumption | Feature 9 | Receive transaction status change notifications, remove TodoCard |
Scheduled task every 5 minutes] F7_QUERY[(Query FAILED records
retryCount < 3)] F7_DELAY{Delay check} F7_CAS[CAS: F → I] F7_DISPATCH{Dispatch by memberType} F7_CUSTOMER[CustomerMigrateService] F7_MERCHANT[MerchantMigrateService] F7_FF[Mark FINAL_FAILED] F7_JOB --> F7_QUERY F7_QUERY --> F7_DELAY F7_DELAY -->|Delay satisfied| F7_CAS F7_DELAY -->|Delay not reached| SKIP1[Skip] F7_CAS --> F7_DISPATCH F7_DISPATCH -->|CUSTOMER| F7_CUSTOMER F7_DISPATCH -->|MERCHANT| F7_MERCHANT F7_QUERY -->|retryCount >= 3| F7_FF end subgraph "Feature 8.1 - Manual Status Modification" F81_API[CounterFacade#putMigrateStatus] F81_PROC[PutMigrateStatusProcessor] F81_DB[(Update t_migrate_record)] F81_API --> F81_PROC --> F81_DB end subgraph "Feature 8.2 - Manual Retry" F82_API[CounterFacade#retryMigrate] F82_PROC[RetryMigrateProcessor] F82_DB[(Reset to PENDING)] F82_API --> F82_PROC --> F82_DB end subgraph "Feature 9 - Transaction Notification Consumption" F9_MQ[exchange.visii.trans
RabbitMQ] F9_HANDLER[TransStatusChangeHandler] F9_CHECK{memberId starts with 1
and status == WV?} F9_REMOVE[removeTodoCard] F9_IGNORE[Ignore] F9_MQ --> F9_HANDLER --> F9_CHECK F9_CHECK -->|Yes| F9_REMOVE F9_CHECK -->|No| F9_IGNORE end
2. Migration Status Enum MigrateStatus
| Code | Enum Value | Description |
|---|---|---|
| P | PENDING | Pending |
| I | IN_PROGRESS | In Progress |
| C | COMPLETED | Completed |
| F | FAILED | Failed (retryable) |
| FF | FINAL_FAILED | Final Failed (exceeds retry limit) |
| S | SKIPPED | Skipped |
3. Feature 7: Automatic Retry Scheduling (MigrateRetryJob)
3.1 Retry Strategy
| retryCount | Delay Time | Action |
|---|---|---|
| 1 | 5 minutes | First retry |
| 2 | 30 minutes | Second retry |
| >= 3 | - | Mark as FINAL_FAILED, no more auto retries |
3.2 Processing Flow
cron: Every 5 minutes] --> CHECK_ENABLED{enabled?} CHECK_ENABLED -->|No| END_DISABLED[Skip] CHECK_ENABLED -->|Yes| CHECK_WINDOW{Within time window?
startHour ~ endHour} CHECK_WINDOW -->|No| END_WINDOW[Skip] CHECK_WINDOW -->|Yes| QUERY[Query FAILED records
retryCount < 3
LIMIT batchSize] QUERY -->|Empty| END_EMPTY[No retryable records] QUERY -->|Has records| LOOP[Iterate each record] LOOP --> CHECK_RETRY{retryCount >= 3?} CHECK_RETRY -->|Yes| MARK_FF[CAS: F → FF
Mark final failed] CHECK_RETRY -->|No| CHECK_DELAY{gmtModified + delay <= now?} CHECK_DELAY -->|No| SKIP_DELAY[Delay not reached, skip] CHECK_DELAY -->|Yes| CAS[CAS: F → I] CAS -->|Success| DISPATCH{memberType?} CAS -->|Conflict| SKIP_CAS[CAS conflict, skip] DISPATCH -->|CUSTOMER| EXEC_C[migrateExecutor submit
customerMigrateService.migrateOne] DISPATCH -->|MERCHANT| EXEC_M[migrateExecutor submit
merchantMigrateService.migrateOne]
3.3 Configuration Items
| Configuration Item | Description | Default Value |
|---|---|---|
elastic.job.task.cron.migrate.retry | Scheduled task cron | 0 0/5 * * * ? * |
| MigrateEngineConfiguration.enabled | Master switch | true |
| MigrateEngineConfiguration.startHour | Execution start hour allowed | - |
| MigrateEngineConfiguration.endHour | Execution end hour allowed | - |
| MigrateEngineConfiguration.batchSize | Batch query size | - |
4. Feature 8.1: Manual Status Modification (putMigrateStatus)
4.1 Interface Definition
| Item | Description |
|---|---|
| Interface | CounterFacade#putMigrateStatus |
| Request Class | PutMigrateStatusRequest |
| Processor | PutMigrateStatusProcessor |
4.2 Request Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| idList | List\<Long\> | Y | Migration record ID list |
| status | String | Y | Target status code (P/C/F/FF/S) |
4.3 Processing Logic
status must be valid enum value] VALIDATE --> QUERY[queryByIds query records] QUERY -->|Empty| SUCCESS_NOOP[Return SUCCESS
No records to update] QUERY -->|Has records| LOOP[Iterate each record] LOOP --> SET_STATUS[Set status = target status] SET_STATUS --> CHECK_PENDING{Target status == PENDING?} CHECK_PENDING -->|Yes| RESET[retryCount = 0
message = null] CHECK_PENDING -->|No| NO_RESET[Keep original retryCount and message] RESET --> UPDATE[updateById] NO_RESET --> UPDATE UPDATE --> SUCCESS[Return SUCCESS]
4.4 Test Cases
| # | Test Case | Expected Result |
|---|---|---|
| 1 | Set FAILED record to COMPLETED | status=C, retryCount/message unchanged |
| 2 | Set FINAL_FAILED record to PENDING | status=P, retryCount=0, message=null |
| 3 | No records found | SUCCESS (no-op) |
| 4 | Set FAILED record to SKIPPED | status=S, retryCount/message unchanged |
5. Feature 8.2: Manual Retry (retryMigrate)
5.1 Interface Definition
| Item | Description |
|---|---|
| Interface | CounterFacade#retryMigrate |
| Request Class | RetryMigrateRequest |
| Processor | RetryMigrateProcessor |
5.2 Request Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| idList | List\<Long\> | Y | Migration record ID list |
5.3 Processing Logic
No records to retry] QUERY -->|Has records| LOOP[Iterate each record] LOOP --> CHECK{status == FAILED
or FINAL_FAILED?} CHECK -->|Yes| RESET[status = PENDING
retryCount = 0
message = null] CHECK -->|No| SKIP[Skip this record] RESET --> UPDATE[updateById] UPDATE --> SUCCESS[Return SUCCESS]
5.4 Test Cases
| # | Test Case | Expected Result |
|---|---|---|
| 1 | FAILED record | Reset to PENDING, retryCount=0, message=null |
| 2 | FINAL_FAILED record | Reset to PENDING, retryCount=0, message=null |
| 3 | COMPLETED record | Skip, no modification |
| 4 | Mixed status (F + FF + C) | Only F and FF reset, C skipped |
| 5 | No records found | SUCCESS (no-op) |
6. Feature 9: Transaction Notification Consumption (TransStatusChangeHandler)
6.1 MQ Configuration
| Item | Value |
|---|---|
| Exchange | exchange.visii.trans |
| Exchange Type | Topic |
| Queue | queue.vis.transStatusChange |
| RoutingKey (sender) | status.${statusCode}.${memberId} |
| RoutingKey (consumer) | status.# |
| Trigger Timing | Transaction first insert (WV), each status change |
| Failure Strategy | try-catch wrapped, failure only log.warn |
6.2 Message Body TransStatusChangeMessage
| Field | Type | Description |
|---|---|---|
| orderId | Long | Order ID |
| memberId | String | Member ID |
| bankCode | String | Bank code |
| status | String | Current status code |
| bankOrderNo | String | Bank order number |
| previousStatus | String | Previous status code (null for first insert) |
| amount | BigDecimal | Transaction amount |
| currency | String | Currency |
| iban | String | IBAN |
| direction | String | Direction (I/O) |
| gmtModified | String | Modification time |
6.3 Processing Flow
exchange.visii.trans] --> PARSE[Parse JSON → TransStatusChangeMessage] PARSE -->|Parse failed| LOG_WARN[log.warn skip] PARSE -->|Success| CHECK_MEMBER{memberId empty?} CHECK_MEMBER -->|Yes| LOG_EMPTY[log.warn skip] CHECK_MEMBER -->|No| CHECK_PREFIX{memberId starts with 1?} CHECK_PREFIX -->|No Merchant| IGNORE[Ignore, do not process] CHECK_PREFIX -->|Yes Individual user| CHECK_STATUS{status == WV?} CHECK_STATUS -->|No| SKIP_STATUS[Not first transaction, skip] CHECK_STATUS -->|Yes| REMOVE[Call migrateNotifyService
.removeTodoCard-memberId-] REMOVE -->|Success| DONE[Processing complete] REMOVE -->|Exception| LOG_FAIL[log.warn does not affect main flow]
6.4 Core Judgment Logic
starts with 1?} B -->|1xxxxxxx
Individual user| C{status == WV?} B -->|2xxxxxxx / 3xxxxxxx
Merchant| D[Directly ignore] C -->|WV First transaction| E[Remove TodoCard] C -->|V / MC / S / P
Other status| F[Skip]
6.5 Test Cases
| # | Test Case | Message Content | Expected Result |
|---|---|---|---|
| 1 | Individual user WV status | memberId=1xxx, status=WV | Call removeTodoCard |
| 2 | Individual user WV + complete fields | Includes orderId/amount/iban etc. | Call removeTodoCard |
| 3 | Individual user V status | memberId=1xxx, status=V | Do not call removeTodoCard |
| 4 | Individual user MC status | memberId=1xxx, status=MC | Do not call removeTodoCard |
| 5 | Individual user S status | memberId=1xxx, status=S | Do not call removeTodoCard |
| 6 | Merchant WV status | memberId=2xxx, status=WV | Do not call removeTodoCard |
| 7 | Merchant starting with 3 | memberId=3xxx, status=WV | Do not call removeTodoCard |
| 8 | Empty message | "" | Not processed |
| 9 | null message | null | Not processed |
| 10 | memberId empty | memberId="", status=WV | Not processed |
| 11 | memberId null | memberId=null, status=WV | Not processed |
| 12 | status null | memberId=1xxx, status=null | Do not call removeTodoCard |
| 13 | Invalid JSON | "invalid json" | No exception thrown, not processed |
| 14 | removeTodoCard throws exception | memberId=1xxx, status=WV | Exception caught, not propagated |
7. Modified File List
7.1 New Files
| File | Description |
|---|---|
facade/domain/TransStatusChangeMessage.java | Transaction status change message body |
facade/request/PutMigrateStatusRequest.java | Manual status modification request |
facade/request/RetryMigrateRequest.java | Manual retry request |
core/dal/configuration/TransStatusChangeConfiguration.java | Transaction notification MQ configuration |
domainservice/mq/TransStatusChangeHandler.java | Transaction notification MQ consumer |
ext/service/manage/PutMigrateStatusProcessor.java | Manual status modification processor |
ext/service/manage/RetryMigrateProcessor.java | Manual retry processor |
ext/daemon/MigrateRetryJob.java | Automatic retry scheduled task |
7.2 Modified Files
| File | Change Description |
|---|---|
facade/enums/MigrateStatus.java | Added FINAL_FAILED("FF") enum value |
facade/CounterFacade.java | Added putMigrateStatus / retryMigrate interface methods |
ext/service/CounterFacadeImpl.java | Added putMigrateStatus / retryMigrate implementation |
core/common/VisConstants.java | Added MIGRATE_MAX_RETRY_COUNT = 3 |
domainservice/repo/MigrateRecordRepository.java | Added queryRetryableRecords / queryByIds methods |
domainservice/repo/impl/MigrateRecordRepositoryImpl.java | Implemented above methods |
domainservice/service/MigrateRecordService.java | Added queryRetryableRecords / queryByIds methods |
domainservice/service/impl/MigrateRecordServiceImpl.java | Implemented above methods |
7.3 Test Files
| File | Case Count | Coverage |
|---|---|---|
MigrateRetryJobTest.java | 8 | Switch/time window/delay/retry/FINAL_FAILED/CAS conflict |
PutMigrateStatusProcessorTest.java | 4 | Set to C/P/S, no records |
RetryMigrateProcessorTest.java | 5 | F reset/FF reset/C skip/mixed/no records |
TransStatusChangeHandlerTest.java | 14 | Individual WV/non-WV/merchant/empty message/exception |
8. Test Guide
8.1 Feature 7 Automatic Retry - Test Steps
Prerequisite: There are FAILED status migration records
| Step | Operation | Verification Point |
|---|---|---|
| 1 | Confirm MigrateEngineConfiguration.enabled = true | Scheduled task running |
| 2 | Prepare status=F, retryCount=1 record, gmtModified 6 minutes ago | 5-minute delay satisfied |
| 3 | Wait for scheduled task trigger (every 5 minutes) | Record status changes to I, then to C or F |
| 4 | Prepare retryCount=2 record, gmtModified 31 minutes ago | 30-minute delay satisfied |
| 5 | Wait for scheduled task trigger | Record is retried |
| 6 | Prepare retryCount=3 FAILED record | No more retries |
| 7 | Wait for scheduled task trigger | Record status changes to FF (FINAL_FAILED) |
| 8 | Set enabled to false | Scheduled task skips, does not query any records |
8.2 Feature 8.1 Manual Status Modification - Test Steps
Interface: CounterFacade#putMigrateStatus
| Step | Operation | Verification Point |
|---|---|---|
| 1 | Call putMigrateStatus, idList=[record ID], status="C" | Record status becomes C, retryCount and message unchanged |
| 2 | Call putMigrateStatus, idList=[record ID], status="P" | Record status becomes P, retryCount=0, message=null |
| 3 | Call putMigrateStatus, idList=[record ID], status="S" | Record status becomes S, retryCount and message unchanged |
| 4 | Call putMigrateStatus, idList=[non-existent ID], status="C" | Return SUCCESS, no records modified |
| 5 | Call putMigrateStatus, idList=null | Return FAIL (parameter validation failed) |
| 6 | Call putMigrateStatus, status="INVALID" | Return FAIL (invalid status code) |
8.3 Feature 8.2 Manual Retry - Test Steps
Interface: CounterFacade#retryMigrate
| Step | Operation | Verification Point |
|---|---|---|
| 1 | Prepare FAILED record, call retryMigrate(idList) | Record becomes PENDING, retryCount=0, message=null |
| 2 | Prepare FINAL_FAILED record, call retryMigrate(idList) | Same as above, FF can also be reset |
| 3 | Prepare COMPLETED record, call retryMigrate(idList) | Record unchanged, still COMPLETED |
| 4 | Pass mixed status record idList | Only F and FF reset, C/S/P unchanged |
| 5 | Wait for engine pickup after reset | PENDING records picked up and executed by engine |
8.4 Feature 9 Transaction Notification - Test Steps
Prerequisite: Visii system sends transaction status change messages to exchange.visii.trans
| Step | Operation | Verification Point |
|---|---|---|
| 1 | Send message: memberId=1xxx, status=WV | VIS calls removeTodoCard, user TodoCard removed |
| 2 | Send message: memberId=1xxx, status=V | VIS receives message but does not process (not WV status) |
| 3 | Send message: memberId=2xxx, status=WV | VIS receives message but ignores (merchant) |
| 4 | Send message: memberId=3xxx, status=WV | VIS receives message but ignores (merchant) |
| 5 | Send message: User has no migration record or TodoCard already removed | removeTodoCard internally skips, no side effects |
| 6 | Simulate CSimple service exception | VIS only prints warn log, does not affect transaction main flow |
| 7 | Send empty message or invalid JSON | VIS does not throw exception, prints log and skips |
8.5 End-to-End Scenario
memberId=1xxx, status=WV MQ->>VIS: TransStatusChangeHandler VIS->>VIS: memberId starts with 1 & status==WV VIS->>CS: removeTodoCard(memberId) end rect rgb(255, 230, 230) Note over Engine: Scenario C: Auto retry 3 times then final failure Engine->>DB: Query retryCount>=3 Engine->>DB: CAS F→FF end rect rgb(230, 255, 230) Note over Counter: Scenario D: Counter manual retry Counter->>VIS: retryMigrate(idList) VIS->>DB: FF→P, retryCount=0 Note over DB: Wait for engine pickup end rect rgb(245, 230, 255) Note over Counter: Scenario E: Counter manual status modification Counter->>VIS: putMigrateStatus(idList, "S") VIS->>DB: status=S (skipped) end
Counter Changes
This change branch: fea-260301-iban-migrate, involves dependency upgrades and four functional points.
Dependency Upgrades
| Dependency | Old Version | New Version |
|---|---|---|
vis-service-facade | 1.1.2-SNAPSHOT | 1.1.4-SNAPSHOT |
visii-service-facade | 1.0.0-SNAPSHOT | 1.0.1-SNAPSHOT |
vis-service-facade1.1.4 addedCUSTOMER_MIGRATE_FILEandMERCHANT_MIGRATE_FILEtwo file type enumsvisii-service-facade1.0.1 addedretryOpenAccountinterfacevis-service-facade1.1.4 addedCounterFacade#putMigrateStatusandCounterFacade#retryMigrateinterfaces
Change 1: File Upload (/upload) Added Two Migration File Types
vis-service-facade's FileType enum added:
| Enum Value | Code | Description |
|---|---|---|
CUSTOMER_MIGRATE_FILE | CMG | CMG-Individual migration file |
MERCHANT_MIGRATE_FILE | MMG | MMG-Merchant migration file |
Since the file upload page (/vis/upload) dynamically renders dropdown options via FileType.values(), it automatically takes effect after upgrading dependencies, no additional code changes needed.
Change 2: Added Retry Open Account Function
Added "Retry Open Account" button on VIS Virtual Account page to support batch retry VAM account opening.
Modified File List:
| File | Change Type | Description |
|---|---|---|
VisiiClient.java | Interface added | Added retryOpenAccount(memberIds, bankCode) method |
VisiiClientImpl.java | Implementation added | Parse comma-separated memberIds, call counterOperateFacade.retryOpenAccount() |
VisAccountController.java | Interface added | Added GET (popup page) and POST (submit processing) two interfaces |
account.html | Frontend button | Added "Retry Open Account" button |
account.js | Frontend logic | Added button click event, popup retry account opening form |
retry_open_account.html | New file | Popup form: memberIds input + bankCode dropdown |
retry_open_account.js | New file | Form submission logic: validation + AJAX POST |
Change 3: Added Put Migrate Status Function
Added "Put Migrate Status" button on VIS Virtual Account page to support manual batch set migration record status.
Modified File List:
| File | Change Type | Description |
|---|---|---|
VisClient.java | Interface added | Added putMigrateStatus(idList, status) method |
VisClientImpl.java | Implementation added | Parse comma-separated idList to List<Long>, call counterFacade.putMigrateStatus() |
VisAccountController.java | Interface added | Added GET (popup page) and POST (submit processing) two interfaces |
account.html | Frontend button | Added "Put Migrate Status" button |
account.js | Frontend logic | Added button click event, popup set migration status form |
put_migrate_status.html | New file | Popup form: idList input + status input |
put_migrate_status.js | New file | Form submission logic: validation + AJAX POST |
Change 4: Added Retry Migrate Record Function
Added "Retry Migrate Record" button on VIS Virtual Account page to support manual batch retry migration records.
Modified File List:
| File | Change Type | Description |
|---|---|---|
VisClient.java | Interface added | Added retryMigrate(idList) method |
VisClientImpl.java | Implementation added | Parse comma-separated idList to List<Long>, call counterFacade.retryMigrate() |
VisAccountController.java | Interface added | Added GET (popup page) and POST (submit processing) two interfaces |
account.html | Frontend button | Added "Retry Migrate Record" button |
account.js | Frontend logic | Added button click event, popup retry migration record form |
retry_migrate_record.html | New file | Popup form: idList input |
retry_migrate_record.js | New file | Form submission logic: validation + AJAX POST |
Use Case Diagram
CMG/MMG] C[Retry VAM Account Opening
Retry Open Account] F[Manual Set Migration Status
Put Migrate Status] G[Retry Migration Record
Retry Migrate Record] end A --> B A --> C A --> F A --> G B --> |Select file type CMG or MMG
Upload file| D[(VIS Service)] C --> |Submit memberIds + bankCode| E[(VISII Service)] F --> |Submit idList + status| D G --> |Submit idList| D
Overall Flow Diagrams
Change 1: Upload Migration File
select CMG or MMG] A3 --> A4[Select file and fill in remarks] A4 --> A5[Submit upload] A5 --> A6{Upload result} A6 --> |Success| A7[Return success message] A6 --> |Failed| A8[Return error message]
Change 2: Retry VAM Account Opening
comma-separated multiple] B4 --> B5[Select bankCode
ZAND or PAYBY] B5 --> B6[Click submit] B6 --> B7{Frontend validation} B7 --> |Validation failed| B8[Prompt field cannot be empty] B7 --> |Validation passed| B9[POST /vis/virtualAccount/
retryOpenAccountSubmit] B9 --> B10[VisiiClientImpl
Parse memberIds to List] B10 --> B11[Call counterOperateFacade
.retryOpenAccount] B11 --> B12{Call result} B12 --> |SUCCESS| B13[Return success message] B12 --> |FAIL| B14[Return error message]
Change 3: Manual Set Migration Status
comma-separated multiple] C4 --> C5[Input status] C5 --> C6[Click submit] C6 --> C7{Frontend validation} C7 --> |Validation failed| C8[Prompt field cannot be empty] C7 --> |Validation passed| C9[POST /vis/migrate/
putMigrateStatusSubmit] C9 --> C10[VisClientImpl
Parse idList to List] C10 --> C11[Call counterFacade
.putMigrateStatus] C11 --> C12{Call result} C12 --> |SUCCESS| C13[Return success message] C12 --> |FAIL| C14[Return error message]
Change 4: Retry Migration Record
comma-separated multiple] D4 --> D5[Click submit] D5 --> D6{Frontend validation} D6 --> |Validation failed| D7[Prompt idList cannot be empty] D6 --> |Validation passed| D8[POST /vis/migrate/
retryMigrateSubmit] D8 --> D9[VisClientImpl
Parse idList to List] D9 --> D10[Call counterFacade
.retryMigrate] D10 --> D11{Call result} D11 --> |SUCCESS| D12[Return success message] D11 --> |FAIL| D13[Return error message]
Test 1: File Upload - Added CMG/MMG File Types
Prerequisite: Environment deployed with vis-service-facade:1.1.4-SNAPSHOT
| Step | Operation | Expected Result |
|---|---|---|
| 1 | Enter VIS File Flow Management page | Page loads normally |
| 2 | Click upload button, open file upload popup | Popup opens normally |
| 3 | Expand fileType dropdown | Dropdown contains CMG-Individual migration file and MMG-Merchant migration file two new options |
| 4 | Select CMG, upload a valid individual migration file, fill in remarks, submit | Submit success, file enters processing flow |
| 5 | Select MMG, upload a valid merchant migration file, fill in remarks, submit | Submit success, file enters processing flow |
| 6 | Upload a file with incorrect format | Return clear error message |
Test 2: Retry Open Account
Prerequisite: Environment deployed with visii-service-facade:1.0.1-SNAPSHOT, and there are failed account opening member records
2.1 Page Display
| Step | Operation | Expected Result |
|---|---|---|
| 1 | Enter VIS Virtual Account page | Page loads normally |
| 2 | Observe toolbar buttons | Next to Retry Trans and Write Off buttons, Retry Open Account button appears |
| 3 | Click Retry Open Account button | 750x550 form popup appears, title is "Retry Open Account" |
2.2 Form Validation
| Step | Operation | Expected Result |
|---|---|---|
| 1 | Do not fill any fields, directly click submit | Prompt bankCode cannot be empty |
| 2 | Fill memberIds, do not select bankCode (if dropdown allows empty selection) | Prompt bankCode cannot be empty |
| 3 | Do not fill memberIds, select bankCode = ZAND, click submit | Prompt memberIds cannot be empty |
| 4 | Click reset button | Form clears and restores |
2.3 Normal Submission
| Step | Operation | Expected Result |
|---|---|---|
| 1 | Input single memberId (e.g., 100001), bankCode select ZAND, submit | Return success message "Success!" |
| 2 | Input multiple memberId, comma-separated (e.g., 100001,100002,100003), bankCode select PAYBY, submit | Return success message "Success!" |
| 3 | Input memberId with spaces (e.g., 100001, 100002 , 100003), submit | Return success message (backend auto trims spaces) |
2.4 Exception Scenarios
| Step | Operation | Expected Result |
|---|---|---|
| 1 | Input non-existent memberId, submit | Return clear error message |
| 2 | Input already successfully opened memberId, submit | Return corresponding business message (not system exception) |
| 3 | Submit when backend service unavailable | Return "Vis retryOpenAccount failed!" error message |
Test 3: Put Migrate Status
Prerequisite: Environment deployed with vis-service-facade:1.1.4-SNAPSHOT, and there are migration records
3.1 Page Display
| Step | Operation | Expected Result |
|---|---|---|
| 1 | Enter VIS Virtual Account page | Page loads normally |
| 2 | Observe toolbar buttons | Next to Retry Open Account button, Put Migrate Status button appears |
| 3 | Click Put Migrate Status button | 750x550 form popup appears, title is "Put Migrate Status" |
3.2 Form Validation
| Step | Operation | Expected Result |
|---|---|---|
| 1 | Do not fill any fields, directly click submit | Prompt status cannot be empty |
| 2 | Do not fill idList, fill status, click submit | Prompt idList cannot be empty |
| 3 | Fill idList, do not fill status, click submit | Prompt status cannot be empty |
| 4 | Click reset button | Form clears and restores |
3.3 Normal Submission
| Step | Operation | Expected Result |
|---|---|---|
| 1 | Input single id (e.g., 100001), fill status, submit | Return success message "Success!" |
| 2 | Input multiple id, comma-separated (e.g., 100001,100002,100003), fill status, submit | Return success message "Success!" |
| 3 | Input id with spaces (e.g., 100001, 100002 , 100003), submit | Return success message (backend auto trims spaces) |
3.4 Exception Scenarios
| Step | Operation | Expected Result |
|---|---|---|
| 1 | Input non-existent id, submit | Return clear error message |
| 2 | Input non-numeric id, submit | Return error message |
| 3 | Submit when backend service unavailable | Return "Vis putMigrateStatus failed!" error message |
Test 4: Retry Migrate Record
Prerequisite: Environment deployed with vis-service-facade:1.1.4-SNAPSHOT, and there are migration records needing retry
4.1 Page Display
| Step | Operation | Expected Result |
|---|---|---|
| 1 | Enter VIS Virtual Account page | Page loads normally |
| 2 | Observe toolbar buttons | Next to Put Migrate Status button, Retry Migrate Record button appears |
| 3 | Click Retry Migrate Record button | 750x550 form popup appears, title is "Retry Migrate Record" |
4.2 Form Validation
| Step | Operation | Expected Result |
|---|---|---|
| 1 | Do not fill idList, directly click submit | Prompt idList cannot be empty |
| 2 | Click reset button | Form clears and restores |
4.3 Normal Submission
| Step | Operation | Expected Result |
|---|---|---|
| 1 | Input single id (e.g., 100001), submit | Return success message "Success!" |
| 2 | Input multiple id, comma-separated (e.g., 100001,100002,100003), submit | Return success message "Success!" |
| 3 | Input id with spaces (e.g., 100001, 100002 , 100003), submit | Return success message (backend auto trims spaces) |
4.4 Exception Scenarios
| Step | Operation | Expected Result |
|---|---|---|
| 1 | Input non-existent id, submit | Return clear error message |
| 2 | Input non-numeric id, submit | Return error message |
| 3 | Submit when backend service unavailable | Return "Vis retryMigrate failed!" error message |
API Interface Reference
| Interface | Method | Path | Parameters |
|---|---|---|---|
| Popup page | GET | /vis/virtualAccount/retryOpenAccount | None |
| Submit processing | POST | /vis/virtualAccount/retryOpenAccountSubmit | memberIds(String, comma-separated), bankCode(String, ZAND/PAYBY) |
| Popup page | GET | /vis/migrate/putMigrateStatus | None |
| Submit processing | POST | /vis/migrate/putMigrateStatusSubmit | idList(String, comma-separated), status(String) |
| Popup page | GET | /vis/migrate/retryMigrateRecord | None |
| Submit processing | POST | /vis/migrate/retryMigrateSubmit | idList(String, comma-separated) |