|
|
|
|
@ -0,0 +1,202 @@
|
|
|
|
|
package com.yau.digitalrmb.payment.infrastructure.persistence;
|
|
|
|
|
|
|
|
|
|
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeyService;
|
|
|
|
|
import com.yau.digitalrmb.institutionidentity.application.InstitutionKeySubject;
|
|
|
|
|
import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentityCryptography;
|
|
|
|
|
import com.yau.digitalrmb.institutionidentity.domain.InstitutionSm2KeyPair;
|
|
|
|
|
import com.yau.digitalrmb.shared.api.ErrorCode;
|
|
|
|
|
import com.yau.digitalrmb.shared.exception.BusinessException;
|
|
|
|
|
import com.yau.digitalrmb.shared.wallet.WalletPrivateKeyCipher;
|
|
|
|
|
import com.yau.digitalrmb.shared.wallet.WalletPrerequisiteReference;
|
|
|
|
|
import org.springframework.dao.DuplicateKeyException;
|
|
|
|
|
import org.springframework.jdbc.core.JdbcTemplate;
|
|
|
|
|
import org.springframework.stereotype.Component;
|
|
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
|
|
|
|
import java.math.BigInteger;
|
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
|
import java.sql.Timestamp;
|
|
|
|
|
import java.time.LocalDateTime;
|
|
|
|
|
import java.util.UUID;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Module five's built-in payer. The prototype deliberately uses a teaching
|
|
|
|
|
* case, not the learner's personal wallet from module three. The wallet is
|
|
|
|
|
* shared by the case, while payment orders remain owned by the current learner
|
|
|
|
|
* through the participant returned by JdbcPaymentResourceRepository.
|
|
|
|
|
*/
|
|
|
|
|
@Component
|
|
|
|
|
public class TeachingPaymentPayer {
|
|
|
|
|
public static final String WALLET_ID =
|
|
|
|
|
"WALLET_A_3A4B5C6D7E8F9A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8A9B0C1D2E3F4";
|
|
|
|
|
private static final String CASE_USER_ID = "00000000-0000-0000-0000-000000000950";
|
|
|
|
|
private static final String BANK_CODE = "BKCHCNBJ00001";
|
|
|
|
|
private static final String BANK_NAME = "中国银行(BKCHCNBJ00001)";
|
|
|
|
|
private static final String CERTIFICATE_SERIAL = "00A1B2C3D4E5F678";
|
|
|
|
|
private static final String ACCOUNT_ID = "ACCOUNT_TEACHING_PAYMENT_PAYER_A";
|
|
|
|
|
private static final String CARD_NUMBER = "6216610100001234567";
|
|
|
|
|
private static final String WALLET_TYPE = "二类钱包";
|
|
|
|
|
private static final BigInteger COIN_SOURCE_BASE = new BigInteger("950000000000");
|
|
|
|
|
|
|
|
|
|
private final JdbcTemplate jdbc;
|
|
|
|
|
private final InstitutionIdentityCryptography cryptography;
|
|
|
|
|
private final WalletPrivateKeyCipher walletCipher;
|
|
|
|
|
private final InstitutionKeyService keyService;
|
|
|
|
|
|
|
|
|
|
public TeachingPaymentPayer(JdbcTemplate jdbc,
|
|
|
|
|
InstitutionIdentityCryptography cryptography,
|
|
|
|
|
WalletPrivateKeyCipher walletCipher,
|
|
|
|
|
InstitutionKeyService keyService) {
|
|
|
|
|
this.jdbc = jdbc;
|
|
|
|
|
this.cryptography = cryptography;
|
|
|
|
|
this.walletCipher = walletCipher;
|
|
|
|
|
this.keyService = keyService;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
public WalletPrerequisiteReference ensure(InstitutionKeySubject subject, String operator) {
|
|
|
|
|
Institution institution = institution(subject);
|
|
|
|
|
ensureWallet();
|
|
|
|
|
ensureCertificate();
|
|
|
|
|
ensureAccount();
|
|
|
|
|
ensureContract();
|
|
|
|
|
ensureCoins(subject, institution.organizationId, operator);
|
|
|
|
|
return new WalletPrerequisiteReference(CASE_USER_ID, 0L, 0L, WALLET_ID,
|
|
|
|
|
BANK_CODE, institution.organizationId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public boolean isTeachingWallet(String walletId) {
|
|
|
|
|
return WALLET_ID.equals(walletId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Institution institution(InstitutionKeySubject subject) {
|
|
|
|
|
return jdbc.query(
|
|
|
|
|
"SELECT bank_code,institution_identifier FROM institution_identifier_application " +
|
|
|
|
|
"WHERE user_id=? AND school_id=? AND class_id=? " +
|
|
|
|
|
"AND status IN ('ISSUED','FEEDBACKED') AND deleted=FALSE " +
|
|
|
|
|
"AND bank_code IS NOT NULL AND institution_identifier IS NOT NULL " +
|
|
|
|
|
"ORDER BY created_at DESC LIMIT 1",
|
|
|
|
|
(rs, row) -> new Institution(rs.getString("bank_code"),
|
|
|
|
|
rs.getString("institution_identifier")),
|
|
|
|
|
subject.getUserId(), subject.getSchoolId(), subject.getClassId())
|
|
|
|
|
.stream().findFirst()
|
|
|
|
|
.orElseThrow(() -> new BusinessException(ErrorCode.VALIDATION_ERROR,
|
|
|
|
|
"请先完成模块一机构标识并取得当前机构标识"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ensureWallet() {
|
|
|
|
|
Integer count = jdbc.queryForObject("SELECT COUNT(*) FROM digital_wallet WHERE wallet_id=?",
|
|
|
|
|
Integer.class, WALLET_ID);
|
|
|
|
|
if (count != null && count > 0) return;
|
|
|
|
|
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 (?,?,?,'ACTIVE',10000.00,0,'TEACHING_PAYMENT_ACTIVATED',CURRENT_TIMESTAMP," +
|
|
|
|
|
"CURRENT_TIMESTAMP,CURRENT_TIMESTAMP)",
|
|
|
|
|
WALLET_ID, CASE_USER_ID, WALLET_TYPE);
|
|
|
|
|
} catch (DuplicateKeyException exception) {
|
|
|
|
|
if (jdbc.queryForObject("SELECT COUNT(*) FROM digital_wallet WHERE wallet_id=?",
|
|
|
|
|
Integer.class, WALLET_ID) == 0) throw exception;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ensureCertificate() {
|
|
|
|
|
Integer count = jdbc.queryForObject("SELECT COUNT(*) FROM wallet_certificate WHERE wallet_id=?",
|
|
|
|
|
Integer.class, WALLET_ID);
|
|
|
|
|
if (count != null && count > 0) return;
|
|
|
|
|
InstitutionSm2KeyPair pair = cryptography.generateSm2KeyPair();
|
|
|
|
|
try {
|
|
|
|
|
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_PAYMENT_ROOT','VALID',CURRENT_TIMESTAMP)",
|
|
|
|
|
CERTIFICATE_SERIAL, WALLET_ID, pair.getPublicKey(), walletCipher.encrypt(pair.getPrivateKey()));
|
|
|
|
|
} catch (DuplicateKeyException exception) {
|
|
|
|
|
if (jdbc.queryForObject("SELECT COUNT(*) FROM wallet_certificate WHERE wallet_id=?",
|
|
|
|
|
Integer.class, WALLET_ID) == 0) throw exception;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ensureAccount() {
|
|
|
|
|
Integer count = jdbc.queryForObject("SELECT COUNT(*) FROM simulated_bank_account WHERE account_id=?",
|
|
|
|
|
Integer.class, ACCOUNT_ID);
|
|
|
|
|
if (count == null || count == 0) {
|
|
|
|
|
try {
|
|
|
|
|
jdbc.update("INSERT INTO simulated_bank_account " +
|
|
|
|
|
"(account_id,user_id,bank_code,bank_name,card_number,card_last4,balance," +
|
|
|
|
|
"frozen_amount,status,created_at,updated_at) VALUES (?,?,?,?,?,?,50000.00,0,'ACTIVE'," +
|
|
|
|
|
"CURRENT_TIMESTAMP,CURRENT_TIMESTAMP)",
|
|
|
|
|
ACCOUNT_ID, CASE_USER_ID, BANK_CODE, BANK_NAME, CARD_NUMBER, "4567");
|
|
|
|
|
} catch (DuplicateKeyException exception) {
|
|
|
|
|
if (jdbc.queryForObject("SELECT COUNT(*) FROM simulated_bank_account WHERE account_id=?",
|
|
|
|
|
Integer.class, ACCOUNT_ID) == 0) throw exception;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Integer binding = jdbc.queryForObject("SELECT COUNT(*) FROM wallet_bank_binding " +
|
|
|
|
|
"WHERE wallet_id=? AND bank_account_id=? AND status='BOUND'",
|
|
|
|
|
Integer.class, WALLET_ID, ACCOUNT_ID);
|
|
|
|
|
if (binding == null || binding == 0) {
|
|
|
|
|
jdbc.update("INSERT INTO wallet_bank_binding (wallet_id,bank_account_id,status,bound_at) " +
|
|
|
|
|
"VALUES (?,?,'BOUND',CURRENT_TIMESTAMP)", WALLET_ID, ACCOUNT_ID);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ensureContract() {
|
|
|
|
|
Integer count = jdbc.queryForObject("SELECT COUNT(*) FROM wallet_contract WHERE wallet_id=?",
|
|
|
|
|
Integer.class, WALLET_ID);
|
|
|
|
|
if (count != null && count > 0) return;
|
|
|
|
|
String original = "CONTRACT|" + WALLET_ID + "|" + WALLET_TYPE + "|50000.00|100000.00|500000.00|500000.00|长期有效|TEACHING";
|
|
|
|
|
jdbc.update("INSERT INTO wallet_contract " +
|
|
|
|
|
"(contract_id,wallet_id,wallet_type,single_payment_limit,daily_payment_limit," +
|
|
|
|
|
"annual_payment_limit,balance_limit,valid_until,original_text,digest,status," +
|
|
|
|
|
"daily_used_amount,daily_counter_date,annual_used_amount,annual_counter_year,created_at,updated_at) " +
|
|
|
|
|
"VALUES (?,?,?,?,?,?,?,NULL,?,?, 'ACTIVE',0,NULL,0,NULL,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP)",
|
|
|
|
|
"CONTRACT_TEACHING_PAYMENT_A", WALLET_ID, WALLET_TYPE, "50000.00", "100000.00",
|
|
|
|
|
"500000.00", "500000.00", original, cryptography.sm3(original));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ensureCoins(InstitutionKeySubject subject, String organizationId, String operator) {
|
|
|
|
|
Integer count = jdbc.queryForObject("SELECT COUNT(*) FROM central_bank_currency_ownership " +
|
|
|
|
|
"WHERE owner_type='WALLET' AND owner_id=? AND status IN ('AVAILABLE','EXCHANGE_LOCKED')",
|
|
|
|
|
Integer.class, WALLET_ID);
|
|
|
|
|
if (count != null && count > 0) return;
|
|
|
|
|
for (int index = 1; index <= 10; index++) {
|
|
|
|
|
String currencyId = "PAYMENT_TEACHING_A_" + String.format("%02d", index);
|
|
|
|
|
String original = "TEACHING_ISSUE|" + currencyId + "|" + organizationId + "|1000.00";
|
|
|
|
|
String requestId = UUID.nameUUIDFromBytes((currencyId + "|" + organizationId)
|
|
|
|
|
.getBytes(StandardCharsets.UTF_8)).toString();
|
|
|
|
|
long sourceId = COIN_SOURCE_BASE.add(BigInteger.valueOf(index)).longValue();
|
|
|
|
|
Integer exists = jdbc.queryForObject("SELECT COUNT(*) FROM commercial_bank_currency WHERE currency_id=?",
|
|
|
|
|
Integer.class, currencyId);
|
|
|
|
|
if (exists == null || exists == 0) {
|
|
|
|
|
jdbc.update("INSERT INTO commercial_bank_currency " +
|
|
|
|
|
"(currency_id,source_currency_id,source_batch_id,issuance_request_id,bank_code," +
|
|
|
|
|
"organization_id,denomination,currency,complete_currency,issuance_ownership_original_text," +
|
|
|
|
|
"issuance_ownership_signature,status,received_at,received_by_user_id,received_by) " +
|
|
|
|
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP,?,?)",
|
|
|
|
|
currencyId, sourceId, COIN_SOURCE_BASE.longValue(), requestId, BANK_CODE, organizationId,
|
|
|
|
|
"1000.00", "DC", "TEACHING_CURRENCY|" + currencyId, original,
|
|
|
|
|
keyService.signCentralBank(subject, original), "可用", subject.getUserId(), operator);
|
|
|
|
|
}
|
|
|
|
|
Integer owned = jdbc.queryForObject("SELECT COUNT(*) FROM central_bank_currency_ownership WHERE currency_id=?",
|
|
|
|
|
Integer.class, currencyId);
|
|
|
|
|
if (owned == null || owned == 0) {
|
|
|
|
|
jdbc.update("INSERT INTO central_bank_currency_ownership " +
|
|
|
|
|
"(currency_id,owner_type,owner_id,status,lock_business_type,lock_business_id,last_transaction_id,updated_at) " +
|
|
|
|
|
"VALUES (?,'WALLET',?,'AVAILABLE',NULL,NULL,NULL,CURRENT_TIMESTAMP)",
|
|
|
|
|
currencyId, WALLET_ID);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static final class Institution {
|
|
|
|
|
private final String bankCode;
|
|
|
|
|
private final String organizationId;
|
|
|
|
|
|
|
|
|
|
private Institution(String bankCode, String organizationId) {
|
|
|
|
|
this.bankCode = bankCode;
|
|
|
|
|
this.organizationId = organizationId;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|