From 4b315995f38c4b0f7cbe57dc51a5cf7bcaeacbf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=B2=85?= <907037276@qq.com> Date: Sat, 5 Sep 2026 19:50:10 +0800 Subject: [PATCH] fix: align payment teaching payee with prototype --- .../service/PaymentTrainingActionService.java | 18 +++--- .../JdbcPaymentResourceRepository.java | 3 + .../TeachingPaymentPayeeWhitelist.java | 55 +++++++++++++++---- .../rest/PaymentControllerTest.java | 31 ++++++++++- 4 files changed, 86 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/yau/digitalrmb/payment/application/service/PaymentTrainingActionService.java b/src/main/java/com/yau/digitalrmb/payment/application/service/PaymentTrainingActionService.java index 490738d..7e96443 100644 --- a/src/main/java/com/yau/digitalrmb/payment/application/service/PaymentTrainingActionService.java +++ b/src/main/java/com/yau/digitalrmb/payment/application/service/PaymentTrainingActionService.java @@ -86,8 +86,12 @@ public class PaymentTrainingActionService { switch (key) { case "01:refresh-payer": return completed(payments.payer(actor)); - case "01:refresh-payee": - return completed(payments.payee(required(request.getPayeeWalletId(), "收款钱包标识"))); + case "01:refresh-payee": { + String payeeWalletId = request.getPayeeWalletId(); + return completed(payeeWalletId == null || payeeWalletId.trim().isEmpty() + ? payments.context(null, actor).getPayee() + : payments.payee(payeeWalletId.trim())); + } case "02:generate-timestamp": return completed(value("timestamp", TIMESTAMP.format(Instant.now()))); case "02:concatenate-payment": @@ -149,8 +153,9 @@ public class PaymentTrainingActionService { private Map paymentSource(PaymentActionRequest request, ExperimentAttemptView attempt, PaymentActor actor) { - String payeeWalletId = required(request.getPayeeWalletId(), "收款钱包标识"); - PaymentContext context = payments.context(payeeWalletId, actor); + String payeeWalletId = request.getPayeeWalletId(); + PaymentContext context = payments.context(payeeWalletId == null || payeeWalletId.trim().isEmpty() + ? null : payeeWalletId.trim(), actor); if (request.getPayerWalletId() != null && !context.getPayer().getWalletId().equals(request.getPayerWalletId().trim())) { throw validation("付款钱包不属于当前用户"); @@ -237,11 +242,6 @@ public class PaymentTrainingActionService { } } - private String required(String value, String name) { - if (value == null || value.trim().isEmpty()) throw validation(name + "不能为空"); - return value.trim(); - } - private String fingerprint(String key, PaymentActionRequest request, ExperimentSubject subject) { Map values = new LinkedHashMap(); values.put("action", key); diff --git a/src/main/java/com/yau/digitalrmb/payment/infrastructure/persistence/JdbcPaymentResourceRepository.java b/src/main/java/com/yau/digitalrmb/payment/infrastructure/persistence/JdbcPaymentResourceRepository.java index c0997c1..b21899c 100644 --- a/src/main/java/com/yau/digitalrmb/payment/infrastructure/persistence/JdbcPaymentResourceRepository.java +++ b/src/main/java/com/yau/digitalrmb/payment/infrastructure/persistence/JdbcPaymentResourceRepository.java @@ -59,6 +59,9 @@ public class JdbcPaymentResourceRepository implements PaymentResourceRepository @Override public PaymentParticipant loadPayee(String payeeWalletId) { + if (teachingPayeeWhitelist.isTeachingWallet(payeeWalletId)) { + return loadParticipant(teachingPayeeWhitelist.resolve(payeeWalletId)); + } return loadParticipant(walletProjection.referenceForWallet(payeeWalletId)); } diff --git a/src/main/java/com/yau/digitalrmb/payment/infrastructure/persistence/TeachingPaymentPayeeWhitelist.java b/src/main/java/com/yau/digitalrmb/payment/infrastructure/persistence/TeachingPaymentPayeeWhitelist.java index 5124cc5..1f9dd02 100644 --- a/src/main/java/com/yau/digitalrmb/payment/infrastructure/persistence/TeachingPaymentPayeeWhitelist.java +++ b/src/main/java/com/yau/digitalrmb/payment/infrastructure/persistence/TeachingPaymentPayeeWhitelist.java @@ -22,9 +22,16 @@ public class TeachingPaymentPayeeWhitelist { private static final String BANK_A = "BKCHCNBJ00001"; private static final String BANK_B = "BKCHCNBJ00002"; private static final String PAYEE_A_USER = "00000000-0000-0000-0000-000000000951"; - private static final String PAYEE_B_USER = "00000000-0000-0000-0000-000000000952"; + private static final String PAYEE_B_USER = "00000000-0000-0000-0000-000000000953"; private static final String PAYEE_A_WALLET = "WALLET_TRAINING_PAYEE_A"; - private static final String PAYEE_B_WALLET = "WALLET_TRAINING_PAYEE_B"; + private static final String PAYEE_B_WALLET = + "WALLET_B_7D8E9F0A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8A9B0C1D2E3F4A5B6"; + private static final PayeeDefinition PAYEE_A = new PayeeDefinition( + PAYEE_A_USER, PAYEE_A_WALLET, "TYPE_II", "CERT_" + PAYEE_A_WALLET, + BANK_A, "教学商业银行A", "ORG_TRAINING_PAYEE_A"); + private static final PayeeDefinition PAYEE_B = new PayeeDefinition( + PAYEE_B_USER, PAYEE_B_WALLET, "一类钱包", "00C3D4E5F6A7B890", + BANK_B, "建设银行(BKCHCNBJ00002)", "ORG_5F6G7B8E9G0W"); private final JdbcTemplate jdbc; private final InstitutionIdentityCryptography cryptography; @@ -39,9 +46,21 @@ public class TeachingPaymentPayeeWhitelist { @Transactional public WalletPrerequisiteReference resolveForDifferentBank(String payerBankCode) { - PayeeDefinition payee = BANK_B.equals(payerBankCode) - ? new PayeeDefinition(PAYEE_A_USER, PAYEE_A_WALLET, BANK_A, "教学商业银行A", "ORG_TRAINING_PAYEE_A") - : new PayeeDefinition(PAYEE_B_USER, PAYEE_B_WALLET, BANK_B, "教学商业银行B", "ORG_TRAINING_PAYEE_B"); + return resolve(BANK_B.equals(payerBankCode) ? PAYEE_A : PAYEE_B); + } + + public boolean isTeachingWallet(String walletId) { + return definitionFor(walletId) != null; + } + + @Transactional + public WalletPrerequisiteReference resolve(String walletId) { + PayeeDefinition payee = definitionFor(walletId); + if (payee == null) throw conflict("教学收款方钱包不存在"); + return resolve(payee); + } + + private WalletPrerequisiteReference resolve(PayeeDefinition payee) { ensureWallet(payee); ensureCertificate(payee); ensureBankAccount(payee); @@ -50,6 +69,12 @@ public class TeachingPaymentPayeeWhitelist { payee.bankCode, payee.organizationId); } + private PayeeDefinition definitionFor(String walletId) { + if (PAYEE_A_WALLET.equals(walletId)) return PAYEE_A; + if (PAYEE_B_WALLET.equals(walletId)) return PAYEE_B; + return null; + } + private void ensureWallet(PayeeDefinition payee) { List wallets = jdbc.query("SELECT user_id,wallet_type,status FROM digital_wallet WHERE wallet_id=?", (rs, row) -> new WalletRow(rs.getString("user_id"), rs.getString("wallet_type"), @@ -58,15 +83,20 @@ public class TeachingPaymentPayeeWhitelist { try { jdbc.update("INSERT INTO digital_wallet (wallet_id,user_id,wallet_type,status,balance,frozen_amount," + "central_bank_confirmation_signature,opened_at,created_at,updated_at) " + - "VALUES (?,?, 'TYPE_II','ACTIVE',0,0,'TEACHING_PAYEE_CONFIRMED',CURRENT_TIMESTAMP," + - "CURRENT_TIMESTAMP,CURRENT_TIMESTAMP)", payee.walletId, payee.userId); + "VALUES (?,?, ?,'ACTIVE',0,0,'TEACHING_PAYEE_CONFIRMED',CURRENT_TIMESTAMP," + + "CURRENT_TIMESTAMP,CURRENT_TIMESTAMP)", payee.walletId, payee.userId, payee.walletType); } catch (DuplicateKeyException exception) { + List afterConflict = jdbc.query( + "SELECT user_id,wallet_type,status FROM digital_wallet WHERE wallet_id=?", + (rs, row) -> new WalletRow(rs.getString("user_id"), rs.getString("wallet_type"), + rs.getString("status")), payee.walletId); + if (afterConflict.isEmpty()) throw conflict("教学收款方的钱包标识或用户标识已被占用"); ensureWallet(payee); } return; } WalletRow wallet = wallets.get(0); - if (!payee.userId.equals(wallet.userId) || !"TYPE_II".equals(wallet.walletType) + if (!payee.userId.equals(wallet.userId) || !payee.walletType.equals(wallet.walletType) || !"ACTIVE".equals(wallet.status)) { throw conflict("教学收款方钱包状态或归属异常"); } @@ -81,7 +111,7 @@ public class TeachingPaymentPayeeWhitelist { jdbc.update("INSERT INTO wallet_certificate (certificate_serial,wallet_id,public_key,encrypted_private_key," + "filing_status,central_bank_root_signature,status,issued_at) " + "VALUES (?,?,?,?, 'REGISTERED','TEACHING_PAYEE_ROOT','VALID',CURRENT_TIMESTAMP)", - "CERT_" + payee.walletId, payee.walletId, keyPair.getPublicKey(), + payee.certificateSerial, payee.walletId, keyPair.getPublicKey(), walletCipher.encrypt(keyPair.getPrivateKey())); } catch (DuplicateKeyException exception) { ensureCertificate(payee); @@ -137,14 +167,19 @@ public class TeachingPaymentPayeeWhitelist { private static final class PayeeDefinition { private final String userId; private final String walletId; + private final String walletType; + private final String certificateSerial; private final String bankCode; private final String bankName; private final String organizationId; - private PayeeDefinition(String userId, String walletId, String bankCode, String bankName, + private PayeeDefinition(String userId, String walletId, String walletType, String certificateSerial, + String bankCode, String bankName, String organizationId) { this.userId = userId; this.walletId = walletId; + this.walletType = walletType; + this.certificateSerial = certificateSerial; this.bankCode = bankCode; this.bankName = bankName; this.organizationId = organizationId; diff --git a/src/test/java/com/yau/digitalrmb/payment/interfaces/rest/PaymentControllerTest.java b/src/test/java/com/yau/digitalrmb/payment/interfaces/rest/PaymentControllerTest.java index 96e5a8c..f71e843 100644 --- a/src/test/java/com/yau/digitalrmb/payment/interfaces/rest/PaymentControllerTest.java +++ b/src/test/java/com/yau/digitalrmb/payment/interfaces/rest/PaymentControllerTest.java @@ -204,11 +204,38 @@ class PaymentControllerTest { .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.data.payer.walletId").value(PAYER_WALLET)) - .andExpect(jsonPath("$.data.payee.walletId").value("WALLET_TRAINING_PAYEE_B")) + .andExpect(jsonPath("$.data.payee.walletId").value( + "WALLET_B_7D8E9F0A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8A9B0C1D2E3F4A5B6")) .andExpect(jsonPath("$.data.payee.bankCode").value("BKCHCNBJ00002")); assertThat(jdbc.queryForObject("SELECT status FROM digital_wallet WHERE wallet_id=?", String.class, - "WALLET_TRAINING_PAYEE_B")).isEqualTo("ACTIVE"); + "WALLET_B_7D8E9F0A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8A9B0C1D2E3F4A5B6")).isEqualTo("ACTIVE"); + } + + @Test + void actionChainUsesTeachingPayeeWithoutASecondWallet() throws Exception { + String created = mockMvc.perform(post("/api/v1/payment/attempts").with(payer())) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + String attemptId = JsonPath.read(created, "$.data.attemptId"); + + executeAction(attemptId, "01", "refresh-payer", payer()); + mockMvc.perform(post("/api/v1/payment/attempts/{id}/steps/01/actions/refresh-payee", attemptId) + .with(payer()).contentType(MediaType.APPLICATION_JSON).content("{}")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.output.walletId").value( + "WALLET_B_7D8E9F0A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8A9B0C1D2E3F4A5B6")) + .andExpect(jsonPath("$.data.output.walletType").value("一类钱包")) + .andExpect(jsonPath("$.data.output.certificateSerial").value("00C3D4E5F6A7B890")) + .andExpect(jsonPath("$.data.output.bankName").value("建设银行(BKCHCNBJ00002)")) + .andExpect(jsonPath("$.data.output.organizationId").value("ORG_5F6G7B8E9G0W")); + executeAction(attemptId, "02", "generate-timestamp", payer()); + mockMvc.perform(post("/api/v1/payment/attempts/{id}/steps/02/actions/concatenate-payment", attemptId) + .with(payer()).contentType(MediaType.APPLICATION_JSON) + .content("{\"payerWalletId\":\"" + PAYER_WALLET + "\",\"payeeWalletId\":\"WALLET_B_7D8E9F0A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8A9B0C1D2E3F4A5B6\",\"amount\":50,\"note\":\"教学支付\"}")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.output.payeeWalletId").value( + "WALLET_B_7D8E9F0A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8A9B0C1D2E3F4A5B6")); } @Test