# Plan: Alipay SDK Integration into Java Expense Services ## Context Python backend had FULL Alipay SDK integration for ~18 expense-control APIs. Java port has `alipay-sdk-java` v4.39.218 in pom.xml + `AlipayClientFactory` bean producing `DefaultAlipayClient` — but 5 expense module services return empty maps with `log.warn("NOT_IMPL")` where they should call the SDK. **5 payment modules already use real SDK calls** as the pattern reference: - `AlipayEmployeeService` (D:\project2\payment-platform\java\src\main\java\com\payment\platform\module\payment\employee\service\AlipayEmployeeService.java) - `AlipayTransferService`, `AlipayEnterpriseService`, `AlipayDepartmentService`, `FacetofaceService` Goal: Replace 18 NOT_IMPL stubs in expense module with real `alipayClientFactory.getClient().execute(request)` calls. ## Canonical Java SDK Pattern From `AlipayEmployeeService.java` (lines 42-70): ```java // 1. Build model AlipayXxxModel model = new AlipayXxxModel(); model.setField(value); // 2. Build request AlipayXxxRequest request = new AlipayXxxRequest(); request.setBizModel(model); // 3. Execute AlipayXxxResponse response = alipayClientFactory.getClient().execute(request); // 4. Check if (!response.isSuccess()) throw new BusinessException(400, "失败: " + (response.getSubMsg() != null ? response.getSubMsg() : response.getMsg())); // 5. Catch catch (AlipayApiException e) { throw new BusinessException(400, "支付宝异常: " + e.getMessage()); } ``` Use wildcard imports: `import com.alipay.api.domain.*;` / `request.*;` / `response.*;` / `AlipayApiException` ## Implementation (4 phases, 5 files) ### Phase 1: InstitutionService (8 APIs) File: `java/src/main/java/com/payment/platform/module/payment/expense/institution/service/InstitutionService.java` Python: `backend/app/plugin/module_payment/expense/institution/service.py` Already has `AlipayClientFactory` injected. | API | Alipay SDK Class | Notes | |-----|-----------------|-------| | `create()` | `AlipayEbppInvoiceInstitutionCreateModel/Request/Response` | Replace UUID fallback with `response.getInstitutionId()` | | `update()` | `AlipayEbppInvoiceInstitutionModifyModel/Request/Response` | Map DTO fields to model | | `delete()` | `AlipayEbppInvoiceInstitutionDeleteModel/Request/Response` | Best-effort: swallow Alipay errors, always clean up local DB | | `detail()` | `AlipayEbppInvoiceInstitutionDetailinfoQueryModel/Request/Response` | Try Alipay first, fall back to DB | | `listScope()` | `AlipayEbppInvoiceInstitutionScopepageinfoQueryModel/Request/Response` | Note SDK typo: `getOnwerOpenIdList()` | | `modifyScope()` | `AlipayEbppInvoiceInstitutionScopeModifyModel/Request/Response` | Python `delete_owner_id_list` → Java `setRemoveOwnerIdList` | | `createIssueRule()` | `AlipayEbppInvoiceIssueruleCreateModel/Request/Response` | Includes issue_target_info_list | | `updateIssueRule()` / `deleteIssueRule()` | Modify/Delete variants | | ### Phase 2: InstitutionScopeSyncService (1 API) File: `java/src/main/java/com/payment/platform/module/payment/expense/institution/service/InstitutionScopeSyncService.java` Already has `AlipayClientFactory`. Replace `scope.modify` stubs for department/employee removal. Errors logged, don't block caller. ### Phase 3: QuotaService (3 stubs) File: `java/src/main/java/com/payment/platform/module/payment/expense/quota/service/QuotaService.java` Python: `backend/app/plugin/module_payment/expense/quota/service.py` Already has `AlipayClientFactory` (adjustInternal + outsourceNotify already work). | Method | Alipay SDK Class | |--------|-----------------| | `create(QuotaCreateDTO)` | `AlipayEbppInvoiceExpensecontrolQuotaCreateModel/Request/Response` | | `expenseCreate(Map)` | Same quota.create API, different DTO packaging | | `expenseQuery(Map)` | `AlipayEbppInvoiceExpensecontrolQuotaQueryModel/Request/Response` | ### Phase 4: RuleService + IssueBatchService (5 APIs) **Need `AlipayClientFactory` injection added** — both currently lack it. **RuleService**: `java/.../expense/rule/service/RuleService.java` Python: `backend/app/plugin/module_payment/expense/rule/service.py` | Method | Alipay SDK Class | |--------|-----------------| | `createExpense(Map)` | `AlipayEbppInvoiceInstitutionExpenseruleCreateModel/Request/Response` | | `modifyExpense(String, Map)` | `AlipayEbppInvoiceInstitutionExpenseruleModifyModel/Request/Response` | | `deleteExpense(String, Map)` | `AlipayEbppInvoiceInstitutionExpenseruleDeleteModel/Request/Response` | **IssueBatchService**: `java/.../expense/quota/service/IssueBatchService.java` | Method | Alipay SDK Class | |--------|-----------------| | `create(Map)` | `AlipayEbppInvoiceExpensecontrolIssuebatchCreateModel/Request/Response` | | `cancel(String)` | `AlipayEbppInvoiceExpensecontrolIssuebatchCancelModel/Request/Response` | ### Phase 5: QuotaService expenseModify + expenseDelete (2 remaining stubs) File: `java/.../expense/quota/service/QuotaService.java` Python: `backend/app/plugin/module_payment/expense/quota/service.py` Already has `AlipayClientFactory` injected. | Method | Alipay SDK Class | Notes | |--------|-----------------|-------| | `expenseModify(String, Map)` | `AlipayEbppInvoiceExpensecontrolQuotaModifyModel/Request/Response` | action=ADD/DEDUCT/MODIFY_SHARE_MODE, need quota_id from local DB via out_biz_no | | `expenseDelete(String)` | `AlipayCommerceEcExpenseDeleteModel/Request/Response` | Simple delete by out_biz_no | ### Phase 6: Python↔Java 全量字段对齐修复 Files: IssueBatchService, RuleService, QuotaService, InstitutionService **CRITICAL (wrong data / SDK rejection):** | File | Method | Fix | |------|--------|-----| | IssueBatchService.create | `target_id`→`owner_id`, `target_type`→`owner_type`, `quota_amount`→`issue_quota` (3 wrong JSON keys) | | IssueBatchService.create | Add `effective_start_date` / `effective_end_date` to model (Python required) | | RuleService.modifyExpense | Add `institution_id` on model (Python required) | | QuotaService.expenseModify | `quota_id` from `data.get("quota_id")`, `outer_source_id` from `data.get("outer_source_id")` — NOT from local DB / parameter | **SEVERE (logic mismatch):** | File | Method | Fix | |------|--------|-----| | InstitutionService.createFullFlow | Rollback: delete Alipay institution if scope/issuerule fails (Python re-raises) | | InstitutionService.createFullFlow | Map `standard_id_info_list` from Alipay response instead of using input data | | RuleService.modifyExpense | `action` from `data.get("action")` not hardcoded | | IssueBatchService.create | Add `issue_desc`, `owner_open_id`, `user_name` optional fields | **MEDIUM (optional fields):** | File | Method | Fix | |------|--------|-----| | RuleService.createExpense | Add `payment_policy`, `consume_mode`, `open_rule_id`, `rule_name` conditionals | | RuleService.modifyExpense | Add `standard_desc`, `open_rule_id`, `payment_policy`, `consume_mode` conditionals | | InstitutionService.createFullFlow | Add `currency` to institution create model | | QuotaService.expenseDelete | Raise BusinessException when not found (match Python), keep SDK gap note | | QuotaService.expenseModify | Add null response guard, add error log before throw, handle share_mode empty string | ### Error handling rules - Normal APIs: Throw `BusinessException` on `!response.isSuccess()` - Institution delete: Swallow Alipay errors (Python best-effort pattern) - Institution detail: Alipay failure → DB fallback - Scope sync: Log + continue, don't block ## Verification 1. `mvn compile` — all SDK classes from `alipay-sdk-java-4.39.218.ALL.jar` compile 2. Field mapping: each Python `data["field_name"]` → Java `model.setFieldName(...)` verified against Python reference 3. SDK naming quirks handled: `removeOwnerIdList` (not delete), `getOnwerOpenIdList()` (typo) 4. All 5 services have `AlipayClientFactory` injection