优化
This commit is contained in:
@@ -5,6 +5,5 @@ spring:
|
|||||||
password: ${FINANCE_DB_PASSWORD}
|
password: ${FINANCE_DB_PASSWORD}
|
||||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
baiyue.clients.warehouse.url: ${WAREHOUSE_SERVICE_URL:}
|
baiyue.clients.warehouse.url: ${WAREHOUSE_SERVICE_URL:}
|
||||||
baiyue.finance.recharge.wechat-qr-code-url: ${FINANCE_RECHARGE_WECHAT_QR_CODE_URL:}
|
baiyue.finance.recharge.qr-code-url: ${FINANCE_RECHARGE_QR_CODE_URL:}
|
||||||
baiyue.finance.recharge.alipay-qr-code-url: ${FINANCE_RECHARGE_ALIPAY_QR_CODE_URL:}
|
|
||||||
baiyue.finance.wallet.default-credit-limit: ${FINANCE_WALLET_DEFAULT_CREDIT_LIMIT:0.00}
|
baiyue.finance.wallet.default-credit-limit: ${FINANCE_WALLET_DEFAULT_CREDIT_LIMIT:0.00}
|
||||||
|
|||||||
@@ -5,6 +5,5 @@ spring:
|
|||||||
username: ${FINANCE_DB_USERNAME:root}
|
username: ${FINANCE_DB_USERNAME:root}
|
||||||
password: ${FINANCE_DB_PASSWORD:123456}
|
password: ${FINANCE_DB_PASSWORD:123456}
|
||||||
baiyue.clients.warehouse.url: ${WAREHOUSE_SERVICE_URL:}
|
baiyue.clients.warehouse.url: ${WAREHOUSE_SERVICE_URL:}
|
||||||
baiyue.finance.recharge.wechat-qr-code-url: ${FINANCE_RECHARGE_WECHAT_QR_CODE_URL:/customer-recharge-qr.svg}
|
baiyue.finance.recharge.qr-code-url: ${FINANCE_RECHARGE_QR_CODE_URL:/customer-recharge-qr.svg}
|
||||||
baiyue.finance.recharge.alipay-qr-code-url: ${FINANCE_RECHARGE_ALIPAY_QR_CODE_URL:/customer-recharge-qr.svg}
|
|
||||||
baiyue.finance.wallet.default-credit-limit: ${FINANCE_WALLET_DEFAULT_CREDIT_LIMIT:0.00}
|
baiyue.finance.wallet.default-credit-limit: ${FINANCE_WALLET_DEFAULT_CREDIT_LIMIT:0.00}
|
||||||
|
|||||||
@@ -4908,7 +4908,6 @@ CREATE TABLE fin_recharge_request (
|
|||||||
customer_id bigint unsigned NOT NULL COMMENT '客户ID和隔离键',
|
customer_id bigint unsigned NOT NULL COMMENT '客户ID和隔离键',
|
||||||
request_no varchar(32) NOT NULL COMMENT '充值申请业务编号RC-yyMMdd-####',
|
request_no varchar(32) NOT NULL COMMENT '充值申请业务编号RC-yyMMdd-####',
|
||||||
amount decimal(20,2) NOT NULL COMMENT '申请充值金额',
|
amount decimal(20,2) NOT NULL COMMENT '申请充值金额',
|
||||||
payment_method varchar(16) NOT NULL COMMENT 'WECHAT或ALIPAY',
|
|
||||||
voucher_file_id bigint unsigned NOT NULL COMMENT '付款截图File ID',
|
voucher_file_id bigint unsigned NOT NULL COMMENT '付款截图File ID',
|
||||||
voucher_file_name varchar(255) NOT NULL COMMENT '付款截图文件名快照',
|
voucher_file_name varchar(255) NOT NULL COMMENT '付款截图文件名快照',
|
||||||
status varchar(16) NOT NULL DEFAULT 'PENDING' COMMENT '审核状态:PENDING待审核、APPROVED已通过、REJECTED已驳回',
|
status varchar(16) NOT NULL DEFAULT 'PENDING' COMMENT '审核状态:PENDING待审核、APPROVED已通过、REJECTED已驳回',
|
||||||
@@ -4926,7 +4925,6 @@ CREATE TABLE fin_recharge_request (
|
|||||||
KEY idx_fin_recharge_customer_status (customer_id,status,created_at,id),
|
KEY idx_fin_recharge_customer_status (customer_id,status,created_at,id),
|
||||||
CONSTRAINT chk_fin_recharge_amount CHECK ((amount > 0)),
|
CONSTRAINT chk_fin_recharge_amount CHECK ((amount > 0)),
|
||||||
CONSTRAINT chk_fin_recharge_customer CHECK ((customer_id > 0)),
|
CONSTRAINT chk_fin_recharge_customer CHECK ((customer_id > 0)),
|
||||||
CONSTRAINT chk_fin_recharge_method CHECK ((payment_method in (_utf8mb4'WECHAT',_utf8mb4'ALIPAY'))),
|
|
||||||
CONSTRAINT chk_fin_recharge_status CHECK ((status in (_utf8mb4'PENDING',_utf8mb4'APPROVED',_utf8mb4'REJECTED')))
|
CONSTRAINT chk_fin_recharge_status CHECK ((status in (_utf8mb4'PENDING',_utf8mb4'APPROVED',_utf8mb4'REJECTED')))
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='客户付款截图充值申请;平台审核通过后入账';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='客户付款截图充值申请;平台审核通过后入账';
|
||||||
|
|
||||||
|
|||||||
+3
-4
@@ -40,19 +40,18 @@ public class ClientFinanceWalletController {
|
|||||||
return FinanceResponses.success(service.rechargeRequests(user.tenantId(), page, size));
|
return FinanceResponses.success(service.rechargeRequests(user.tenantId(), page, size));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/recharge-channels")
|
@GetMapping("/recharge-channel")
|
||||||
@PreAuthorize("hasAuthority('merchant:finance:recharge:create')")
|
@PreAuthorize("hasAuthority('merchant:finance:recharge:create')")
|
||||||
public ApiResponse<?> channels() { return FinanceResponses.success(service.channels()); }
|
public ApiResponse<?> channel() { return FinanceResponses.success(service.channel()); }
|
||||||
|
|
||||||
@PostMapping(value = "/recharge-requests", consumes = "multipart/form-data")
|
@PostMapping(value = "/recharge-requests", consumes = "multipart/form-data")
|
||||||
@PreAuthorize("hasAuthority('merchant:finance:recharge:create')")
|
@PreAuthorize("hasAuthority('merchant:finance:recharge:create')")
|
||||||
public ApiResponse<?> submit(@AuthenticationPrincipal AuthenticatedUser user,
|
public ApiResponse<?> submit(@AuthenticationPrincipal AuthenticatedUser user,
|
||||||
@RequestHeader("Idempotency-Key") String idempotencyKey,
|
@RequestHeader("Idempotency-Key") String idempotencyKey,
|
||||||
@RequestParam BigDecimal amount,
|
@RequestParam BigDecimal amount,
|
||||||
@RequestParam String paymentMethod,
|
|
||||||
@RequestPart("file") MultipartFile file) {
|
@RequestPart("file") MultipartFile file) {
|
||||||
return FinanceResponses.success(service.submitRecharge(user.tenantId(), user.userId(), user.displayName(),
|
return FinanceResponses.success(service.submitRecharge(user.tenantId(), user.userId(), user.displayName(),
|
||||||
idempotencyKey, amount, paymentMethod, file));
|
idempotencyKey, amount, file));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/recharge-requests/{requestId}/voucher")
|
@GetMapping("/recharge-requests/{requestId}/voucher")
|
||||||
|
|||||||
@@ -33,13 +33,12 @@ public final class FinanceWalletDtos {
|
|||||||
String remark, LocalDateTime createdAt) { }
|
String remark, LocalDateTime createdAt) { }
|
||||||
|
|
||||||
public record RechargeRequest(long requestId, String requestNo, BigDecimal amount,
|
public record RechargeRequest(long requestId, String requestNo, BigDecimal amount,
|
||||||
String paymentMethod, long voucherFileId, String voucherFileName,
|
long voucherFileId, String voucherFileName,
|
||||||
String status, int version, String submittedByName,
|
String status, int version, String submittedByName,
|
||||||
String reviewedByName, String reviewRemark,
|
String reviewedByName, String reviewRemark,
|
||||||
LocalDateTime createdAt, LocalDateTime reviewedAt) { }
|
LocalDateTime createdAt, LocalDateTime reviewedAt) { }
|
||||||
|
|
||||||
public record RechargeChannel(String paymentMethod, String displayName,
|
public record RechargeChannel(boolean enabled, String qrCodeUrl) { }
|
||||||
boolean enabled, String qrCodeUrl) { }
|
|
||||||
|
|
||||||
public record RechargeReviewRequest(String decision, String remark, int expectedVersion) { }
|
public record RechargeReviewRequest(String decision, String remark, int expectedVersion) { }
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ import java.util.Set;
|
|||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class FinanceWalletService {
|
public class FinanceWalletService {
|
||||||
private static final Set<String> PAYMENT_METHODS = Set.of("WECHAT", "ALIPAY");
|
|
||||||
private static final Set<String> ADJUSTMENT_PAYMENT_METHODS = Set.of("BANK_TRANSFER", "CASH", "OTHER");
|
private static final Set<String> ADJUSTMENT_PAYMENT_METHODS = Set.of("BANK_TRANSFER", "CASH", "OTHER");
|
||||||
private static final Set<String> IMAGE_TYPES = Set.of("image/jpeg", "image/png");
|
private static final Set<String> IMAGE_TYPES = Set.of("image/jpeg", "image/png");
|
||||||
|
|
||||||
@@ -51,15 +50,13 @@ public class FinanceWalletService {
|
|||||||
private final FinanceFileClient fileClient;
|
private final FinanceFileClient fileClient;
|
||||||
private final FinanceDocumentNumberGenerator numberGenerator;
|
private final FinanceDocumentNumberGenerator numberGenerator;
|
||||||
private final String internalToken;
|
private final String internalToken;
|
||||||
private final String wechatQrCodeUrl;
|
private final String rechargeQrCodeUrl;
|
||||||
private final String alipayQrCodeUrl;
|
|
||||||
|
|
||||||
public FinanceWalletService(JdbcTemplate jdbc, ObjectMapper objectMapper,
|
public FinanceWalletService(JdbcTemplate jdbc, ObjectMapper objectMapper,
|
||||||
FinanceFileClient fileClient,
|
FinanceFileClient fileClient,
|
||||||
FinanceDocumentNumberGenerator numberGenerator,
|
FinanceDocumentNumberGenerator numberGenerator,
|
||||||
@Value("${baiyue.security.internal-service-token:}") String internalToken,
|
@Value("${baiyue.security.internal-service-token:}") String internalToken,
|
||||||
@Value("${baiyue.finance.recharge.wechat-qr-code-url:}") String wechatQrCodeUrl,
|
@Value("${baiyue.finance.recharge.qr-code-url:}") String rechargeQrCodeUrl,
|
||||||
@Value("${baiyue.finance.recharge.alipay-qr-code-url:}") String alipayQrCodeUrl,
|
|
||||||
@Value("${spring.profiles.active:}") String activeProfiles,
|
@Value("${spring.profiles.active:}") String activeProfiles,
|
||||||
@Value("${DEPLOY_ENV:}") String deployEnvironment) {
|
@Value("${DEPLOY_ENV:}") String deployEnvironment) {
|
||||||
this.jdbc = jdbc;
|
this.jdbc = jdbc;
|
||||||
@@ -69,8 +66,7 @@ public class FinanceWalletService {
|
|||||||
this.internalToken = internalToken;
|
this.internalToken = internalToken;
|
||||||
boolean development = "development".equalsIgnoreCase(deployEnvironment.trim())
|
boolean development = "development".equalsIgnoreCase(deployEnvironment.trim())
|
||||||
|| List.of(activeProfiles.split(",")).stream().map(String::trim).anyMatch("dev"::equalsIgnoreCase);
|
|| List.of(activeProfiles.split(",")).stream().map(String::trim).anyMatch("dev"::equalsIgnoreCase);
|
||||||
this.wechatQrCodeUrl = rechargeQrCode(wechatQrCodeUrl, development);
|
this.rechargeQrCodeUrl = rechargeQrCode(rechargeQrCodeUrl, development);
|
||||||
this.alipayQrCodeUrl = rechargeQrCode(alipayQrCodeUrl, development);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String rechargeQrCode(String configured, boolean development) {
|
private static String rechargeQrCode(String configured, boolean development) {
|
||||||
@@ -104,22 +100,20 @@ public class FinanceWalletService {
|
|||||||
return WalletPage.of(rows, total == null ? 0 : total, page, size);
|
return WalletPage.of(rows, total == null ? 0 : total, page, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<RechargeChannel> channels() {
|
public RechargeChannel channel() {
|
||||||
return List.of(new RechargeChannel("WECHAT", "微信支付", wechatQrCodeUrl != null, wechatQrCodeUrl),
|
return new RechargeChannel(rechargeQrCodeUrl != null, rechargeQrCodeUrl);
|
||||||
new RechargeChannel("ALIPAY", "支付宝", alipayQrCodeUrl != null, alipayQrCodeUrl));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public RechargeRequest submitRecharge(long customerId, long operatorId, String operatorName,
|
public RechargeRequest submitRecharge(long customerId, long operatorId, String operatorName,
|
||||||
String idempotencyKey, BigDecimal amount,
|
String idempotencyKey, BigDecimal amount,
|
||||||
String paymentMethod, MultipartFile file) {
|
MultipartFile file) {
|
||||||
requirePositive(customerId, "客户"); requirePositive(operatorId, "操作人");
|
requirePositive(customerId, "客户"); requirePositive(operatorId, "操作人");
|
||||||
BigDecimal normalizedAmount = money(amount);
|
BigDecimal normalizedAmount = money(amount);
|
||||||
String method = paymentMethod(paymentMethod);
|
RechargeChannel channel = channel();
|
||||||
RechargeChannel channel = channels().stream().filter(value -> value.paymentMethod().equals(method)).findFirst().orElseThrow();
|
if (!channel.enabled()) throw error("FIN-WALLET-CHANNEL-DISABLED", "收款二维码未配置");
|
||||||
if (!channel.enabled()) throw error("FIN-WALLET-CHANNEL-DISABLED", channel.displayName() + "收款二维码未配置");
|
|
||||||
validateImage(file);
|
validateImage(file);
|
||||||
String requestHash = hash(Map.of("amount", normalizedAmount.toPlainString(), "paymentMethod", method,
|
String requestHash = hash(Map.of("amount", normalizedAmount.toPlainString(),
|
||||||
"fileName", String.valueOf(file.getOriginalFilename()), "contentType", String.valueOf(file.getContentType()),
|
"fileName", String.valueOf(file.getOriginalFilename()), "contentType", String.valueOf(file.getContentType()),
|
||||||
"size", file.getSize(), "fileHash", fileHash(file)));
|
"size", file.getSize(), "fileHash", fileHash(file)));
|
||||||
Long replay = reserve(customerId, operatorId, "WALLET_RECHARGE_SUBMIT", idempotencyKey, requestHash);
|
Long replay = reserve(customerId, operatorId, "WALLET_RECHARGE_SUBMIT", idempotencyKey, requestHash);
|
||||||
@@ -138,12 +132,12 @@ public class FinanceWalletService {
|
|||||||
KeyHolder holder = new GeneratedKeyHolder();
|
KeyHolder holder = new GeneratedKeyHolder();
|
||||||
jdbc.update(connection -> {
|
jdbc.update(connection -> {
|
||||||
PreparedStatement statement = connection.prepareStatement(
|
PreparedStatement statement = connection.prepareStatement(
|
||||||
"INSERT INTO fin_recharge_request(customer_id,request_no,amount,payment_method,voucher_file_id,voucher_file_name,status,version,submitted_by,submitted_by_name) VALUES(?,?,?,?,?,?,'PENDING',0,?,?)",
|
"INSERT INTO fin_recharge_request(customer_id,request_no,amount,voucher_file_id,voucher_file_name,status,version,submitted_by,submitted_by_name) VALUES(?,?,?,?,?,'PENDING',0,?,?)",
|
||||||
Statement.RETURN_GENERATED_KEYS);
|
Statement.RETURN_GENERATED_KEYS);
|
||||||
statement.setLong(1, customerId); statement.setString(2, requestNo);
|
statement.setLong(1, customerId); statement.setString(2, requestNo);
|
||||||
statement.setBigDecimal(3, normalizedAmount); statement.setString(4, method);
|
statement.setBigDecimal(3, normalizedAmount);
|
||||||
statement.setLong(5, upload.data().fileId()); statement.setString(6, upload.data().fileName());
|
statement.setLong(4, upload.data().fileId()); statement.setString(5, upload.data().fileName());
|
||||||
statement.setLong(7, operatorId); statement.setString(8, clean(operatorName, 128));
|
statement.setLong(6, operatorId); statement.setString(7, clean(operatorName, 128));
|
||||||
return statement;
|
return statement;
|
||||||
}, holder);
|
}, holder);
|
||||||
long requestId = generatedId(holder);
|
long requestId = generatedId(holder);
|
||||||
@@ -173,7 +167,7 @@ public class FinanceWalletService {
|
|||||||
WalletAccount afterAccount = rechargeAccount(account, before.amount());
|
WalletAccount afterAccount = rechargeAccount(account, before.amount());
|
||||||
updateAccount(beforeCustomerId(requestId), afterAccount, account.version(), operatorId);
|
updateAccount(beforeCustomerId(requestId), afterAccount, account.version(), operatorId);
|
||||||
insertLedger(beforeCustomerId(requestId), "RECHARGE", before.amount(), afterAccount,
|
insertLedger(beforeCustomerId(requestId), "RECHARGE", before.amount(), afterAccount,
|
||||||
"CLIENT_RECHARGE", before.requestNo(), LocalDate.now(), before.paymentMethod(), null,
|
"CLIENT_RECHARGE", before.requestNo(), LocalDate.now(), null, null,
|
||||||
operatorId, operatorName, request.remark());
|
operatorId, operatorName, request.remark());
|
||||||
}
|
}
|
||||||
RechargeRequest after = requiredRecharge(requestId, null);
|
RechargeRequest after = requiredRecharge(requestId, null);
|
||||||
@@ -587,7 +581,7 @@ public class FinanceWalletService {
|
|||||||
|
|
||||||
private RechargeRequest rechargeRow(java.sql.ResultSet rs) throws java.sql.SQLException {
|
private RechargeRequest rechargeRow(java.sql.ResultSet rs) throws java.sql.SQLException {
|
||||||
return new RechargeRequest(rs.getLong("id"), rs.getString("request_no"), rs.getBigDecimal("amount"),
|
return new RechargeRequest(rs.getLong("id"), rs.getString("request_no"), rs.getBigDecimal("amount"),
|
||||||
rs.getString("payment_method"), rs.getLong("voucher_file_id"), rs.getString("voucher_file_name"),
|
rs.getLong("voucher_file_id"), rs.getString("voucher_file_name"),
|
||||||
rs.getString("status"), rs.getInt("version"), rs.getString("submitted_by_name"),
|
rs.getString("status"), rs.getInt("version"), rs.getString("submitted_by_name"),
|
||||||
rs.getString("reviewed_by_name"), rs.getString("review_remark"), timestamp(rs.getTimestamp("created_at")),
|
rs.getString("reviewed_by_name"), rs.getString("review_remark"), timestamp(rs.getTimestamp("created_at")),
|
||||||
timestamp(rs.getTimestamp("reviewed_at")));
|
timestamp(rs.getTimestamp("reviewed_at")));
|
||||||
@@ -633,11 +627,6 @@ public class FinanceWalletService {
|
|||||||
if (normalized == null) throw bad("业务单号不能为空");
|
if (normalized == null) throw bad("业务单号不能为空");
|
||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
private static String paymentMethod(String value) {
|
|
||||||
String normalized = upper(value);
|
|
||||||
if (!PAYMENT_METHODS.contains(normalized)) throw bad("付款方式无效");
|
|
||||||
return normalized;
|
|
||||||
}
|
|
||||||
private static void validateImage(MultipartFile file) {
|
private static void validateImage(MultipartFile file) {
|
||||||
if (file == null || file.isEmpty()) throw bad("请上传付款截图");
|
if (file == null || file.isEmpty()) throw bad("请上传付款截图");
|
||||||
if (!IMAGE_TYPES.contains(String.valueOf(file.getContentType()).toLowerCase(Locale.ROOT))) throw bad("付款截图仅支持 JPG 或 PNG");
|
if (!IMAGE_TYPES.contains(String.valueOf(file.getContentType()).toLowerCase(Locale.ROOT))) throw bad("付款截图仅支持 JPG 或 PNG");
|
||||||
|
|||||||
@@ -8,5 +8,4 @@ spring:
|
|||||||
baiyue:
|
baiyue:
|
||||||
finance:
|
finance:
|
||||||
recharge:
|
recharge:
|
||||||
wechat-qr-code-url: ${FINANCE_RECHARGE_WECHAT_QR_CODE_URL:/customer-recharge-qr.svg}
|
qr-code-url: ${FINANCE_RECHARGE_QR_CODE_URL:/customer-recharge-qr.svg}
|
||||||
alipay-qr-code-url: ${FINANCE_RECHARGE_ALIPAY_QR_CODE_URL:/customer-recharge-qr.svg}
|
|
||||||
|
|||||||
@@ -467,7 +467,6 @@ CREATE TABLE IF NOT EXISTS fin_recharge_request (
|
|||||||
customer_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和隔离键',
|
customer_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和隔离键',
|
||||||
request_no VARCHAR(32) NOT NULL COMMENT '充值申请业务编号RC-yyMMdd-####',
|
request_no VARCHAR(32) NOT NULL COMMENT '充值申请业务编号RC-yyMMdd-####',
|
||||||
amount DECIMAL(20,2) NOT NULL COMMENT '申请充值金额',
|
amount DECIMAL(20,2) NOT NULL COMMENT '申请充值金额',
|
||||||
payment_method VARCHAR(16) NOT NULL COMMENT 'WECHAT或ALIPAY',
|
|
||||||
voucher_file_id BIGINT UNSIGNED NOT NULL COMMENT '付款截图File ID',
|
voucher_file_id BIGINT UNSIGNED NOT NULL COMMENT '付款截图File ID',
|
||||||
voucher_file_name VARCHAR(255) NOT NULL COMMENT '付款截图文件名快照',
|
voucher_file_name VARCHAR(255) NOT NULL COMMENT '付款截图文件名快照',
|
||||||
status VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '审核状态:PENDING待审核、APPROVED已通过、REJECTED已驳回',
|
status VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '审核状态:PENDING待审核、APPROVED已通过、REJECTED已驳回',
|
||||||
@@ -485,7 +484,6 @@ CREATE TABLE IF NOT EXISTS fin_recharge_request (
|
|||||||
KEY idx_fin_recharge_customer_status (customer_id, status, created_at, id),
|
KEY idx_fin_recharge_customer_status (customer_id, status, created_at, id),
|
||||||
CONSTRAINT chk_fin_recharge_customer CHECK (customer_id > 0),
|
CONSTRAINT chk_fin_recharge_customer CHECK (customer_id > 0),
|
||||||
CONSTRAINT chk_fin_recharge_amount CHECK (amount > 0),
|
CONSTRAINT chk_fin_recharge_amount CHECK (amount > 0),
|
||||||
CONSTRAINT chk_fin_recharge_method CHECK (payment_method IN ('WECHAT','ALIPAY')),
|
|
||||||
CONSTRAINT chk_fin_recharge_status CHECK (status IN ('PENDING','APPROVED','REJECTED'))
|
CONSTRAINT chk_fin_recharge_status CHECK (status IN ('PENDING','APPROVED','REJECTED'))
|
||||||
) ENGINE=InnoDB COMMENT='客户付款截图充值申请;平台审核通过后入账';
|
) ENGINE=InnoDB COMMENT='客户付款截图充值申请;平台审核通过后入账';
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class FinanceWalletBehaviorTest {
|
|||||||
JdbcTemplate jdbc = mock(JdbcTemplate.class);
|
JdbcTemplate jdbc = mock(JdbcTemplate.class);
|
||||||
when(jdbc.query(startsWith("SELECT currency_code"), any(RowMapper.class), eq(9L))).thenReturn(List.of());
|
when(jdbc.query(startsWith("SELECT currency_code"), any(RowMapper.class), eq(9L))).thenReturn(List.of());
|
||||||
|
|
||||||
var account = service(jdbc, "https://pay.example/wechat.png", "").account(9L);
|
var account = service(jdbc, "https://pay.example/qr.png").account(9L);
|
||||||
|
|
||||||
assertThat(account.actualBalance()).isEqualByComparingTo("0.00");
|
assertThat(account.actualBalance()).isEqualByComparingTo("0.00");
|
||||||
assertThat(account.frozenBalance()).isEqualByComparingTo("0.00");
|
assertThat(account.frozenBalance()).isEqualByComparingTo("0.00");
|
||||||
@@ -34,28 +34,25 @@ class FinanceWalletBehaviorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void rechargeChannelsAreEnabledOnlyWhenQrCodeIsConfigured() {
|
void rechargeChannelsAreEnabledOnlyWhenQrCodeIsConfigured() {
|
||||||
var channels = service(mock(JdbcTemplate.class), "https://pay.example/wechat.png", "").channels();
|
var channel = service(mock(JdbcTemplate.class), "https://pay.example/qr.png").channel();
|
||||||
|
|
||||||
assertThat(channels).satisfiesExactly(
|
assertThat(channel.enabled()).isTrue();
|
||||||
wechat -> { assertThat(wechat.paymentMethod()).isEqualTo("WECHAT"); assertThat(wechat.enabled()).isTrue(); },
|
assertThat(channel.qrCodeUrl()).isEqualTo("https://pay.example/qr.png");
|
||||||
alipay -> { assertThat(alipay.paymentMethod()).isEqualTo("ALIPAY"); assertThat(alipay.enabled()).isFalse(); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void developmentProfileUsesTheNonPayableSampleQrCode() {
|
void developmentProfileUsesTheNonPayableSampleQrCode() {
|
||||||
var channels = new FinanceWalletService(mock(JdbcTemplate.class), new ObjectMapper().findAndRegisterModules(),
|
var channels = new FinanceWalletService(mock(JdbcTemplate.class), new ObjectMapper().findAndRegisterModules(),
|
||||||
mock(FinanceFileClient.class), mock(FinanceDocumentNumberGenerator.class), "token", "", "",
|
mock(FinanceFileClient.class), mock(FinanceDocumentNumberGenerator.class), "token", "",
|
||||||
"cn", "development").channels();
|
"cn", "development").channel();
|
||||||
|
|
||||||
assertThat(channels).allSatisfy(channel -> {
|
assertThat(channels.enabled()).isTrue();
|
||||||
assertThat(channel.enabled()).isTrue();
|
assertThat(channels.qrCodeUrl()).isEqualTo("/customer-recharge-qr.svg");
|
||||||
assertThat(channel.qrCodeUrl()).isEqualTo("/customer-recharge-qr.svg");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void deductionMayMakeActualBalanceNegative() {
|
void deductionMayMakeActualBalanceNegative() {
|
||||||
FinanceWalletService service = service(mock(JdbcTemplate.class), "x", "y");
|
FinanceWalletService service = service(mock(JdbcTemplate.class), "x");
|
||||||
WalletAccount initial = service.accountState(new BigDecimal("10.00"), BigDecimal.ZERO,
|
WalletAccount initial = service.accountState(new BigDecimal("10.00"), BigDecimal.ZERO,
|
||||||
BigDecimal.ZERO, BigDecimal.ZERO, null, 2);
|
BigDecimal.ZERO, BigDecimal.ZERO, null, 2);
|
||||||
|
|
||||||
@@ -71,7 +68,7 @@ class FinanceWalletBehaviorTest {
|
|||||||
JdbcTemplate jdbc = mock(JdbcTemplate.class);
|
JdbcTemplate jdbc = mock(JdbcTemplate.class);
|
||||||
when(jdbc.query(startsWith("SELECT currency_code"), any(RowMapper.class), eq(9L))).thenReturn(List.of());
|
when(jdbc.query(startsWith("SELECT currency_code"), any(RowMapper.class), eq(9L))).thenReturn(List.of());
|
||||||
|
|
||||||
var account = service(jdbc, "x", "y").account(9L);
|
var account = service(jdbc, "x").account(9L);
|
||||||
|
|
||||||
assertThat(account.creditLimit()).isZero();
|
assertThat(account.creditLimit()).isZero();
|
||||||
assertThat(account.availableCredit()).isZero();
|
assertThat(account.availableCredit()).isZero();
|
||||||
@@ -82,7 +79,7 @@ class FinanceWalletBehaviorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void deductionUsesCashBeforeCreditAndRechargeRepaysCreditFirst() {
|
void deductionUsesCashBeforeCreditAndRechargeRepaysCreditFirst() {
|
||||||
FinanceWalletService service = service(mock(JdbcTemplate.class), "x", "y");
|
FinanceWalletService service = service(mock(JdbcTemplate.class), "x");
|
||||||
WalletAccount initial = service.accountState(new BigDecimal("100.00"), BigDecimal.ZERO,
|
WalletAccount initial = service.accountState(new BigDecimal("100.00"), BigDecimal.ZERO,
|
||||||
new BigDecimal("50.00"), BigDecimal.ZERO, LocalDate.now().plusDays(7), 3);
|
new BigDecimal("50.00"), BigDecimal.ZERO, LocalDate.now().plusDays(7), 3);
|
||||||
|
|
||||||
@@ -100,7 +97,7 @@ class FinanceWalletBehaviorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void deductionNeverConsumesCashReservedByAnActiveHold() {
|
void deductionNeverConsumesCashReservedByAnActiveHold() {
|
||||||
FinanceWalletService service = service(mock(JdbcTemplate.class), "x", "y");
|
FinanceWalletService service = service(mock(JdbcTemplate.class), "x");
|
||||||
WalletAccount initial = service.accountState(new BigDecimal("100.00"), new BigDecimal("50.00"),
|
WalletAccount initial = service.accountState(new BigDecimal("100.00"), new BigDecimal("50.00"),
|
||||||
new BigDecimal("80.00"), new BigDecimal("20.00"), LocalDate.now().plusDays(7), 4);
|
new BigDecimal("80.00"), new BigDecimal("20.00"), LocalDate.now().plusDays(7), 4);
|
||||||
|
|
||||||
@@ -116,7 +113,7 @@ class FinanceWalletBehaviorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void rechargeRepaysUsedCreditWithoutReleasingFrozenCredit() {
|
void rechargeRepaysUsedCreditWithoutReleasingFrozenCredit() {
|
||||||
FinanceWalletService service = service(mock(JdbcTemplate.class), "x", "y");
|
FinanceWalletService service = service(mock(JdbcTemplate.class), "x");
|
||||||
WalletAccount initial = service.accountState(new BigDecimal("5.00"), new BigDecimal("25.00"),
|
WalletAccount initial = service.accountState(new BigDecimal("5.00"), new BigDecimal("25.00"),
|
||||||
new BigDecimal("100.00"), new BigDecimal("40.00"), new BigDecimal("20.00"),
|
new BigDecimal("100.00"), new BigDecimal("40.00"), new BigDecimal("20.00"),
|
||||||
LocalDate.now().plusDays(7), 5);
|
LocalDate.now().plusDays(7), 5);
|
||||||
@@ -133,7 +130,7 @@ class FinanceWalletBehaviorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void accountStateRejectsCreditUsedAndFrozenAboveTheLimit() {
|
void accountStateRejectsCreditUsedAndFrozenAboveTheLimit() {
|
||||||
FinanceWalletService service = service(mock(JdbcTemplate.class), "x", "y");
|
FinanceWalletService service = service(mock(JdbcTemplate.class), "x");
|
||||||
|
|
||||||
assertThatThrownBy(() -> service.accountState(BigDecimal.ZERO, new BigDecimal("20.00"),
|
assertThatThrownBy(() -> service.accountState(BigDecimal.ZERO, new BigDecimal("20.00"),
|
||||||
new BigDecimal("50.00"), new BigDecimal("31.00"), new BigDecimal("20.00"),
|
new BigDecimal("50.00"), new BigDecimal("31.00"), new BigDecimal("20.00"),
|
||||||
@@ -144,7 +141,7 @@ class FinanceWalletBehaviorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void accountStateAllowsBalanceToBecomeNegativeWhileFundsRemainFrozen() {
|
void accountStateAllowsBalanceToBecomeNegativeWhileFundsRemainFrozen() {
|
||||||
FinanceWalletService service = service(mock(JdbcTemplate.class), "x", "y");
|
FinanceWalletService service = service(mock(JdbcTemplate.class), "x");
|
||||||
|
|
||||||
WalletAccount account = service.accountState(new BigDecimal("9.99"), new BigDecimal("10.00"),
|
WalletAccount account = service.accountState(new BigDecimal("9.99"), new BigDecimal("10.00"),
|
||||||
new BigDecimal("20.00"), BigDecimal.ZERO, LocalDate.now().plusDays(7), 1);
|
new BigDecimal("20.00"), BigDecimal.ZERO, LocalDate.now().plusDays(7), 1);
|
||||||
@@ -155,7 +152,7 @@ class FinanceWalletBehaviorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void accountStateRejectsCreditFrozenAboveTotalFrozenAmount() {
|
void accountStateRejectsCreditFrozenAboveTotalFrozenAmount() {
|
||||||
FinanceWalletService service = service(mock(JdbcTemplate.class), "x", "y");
|
FinanceWalletService service = service(mock(JdbcTemplate.class), "x");
|
||||||
|
|
||||||
assertThatThrownBy(() -> service.accountState(new BigDecimal("100.00"), new BigDecimal("10.00"),
|
assertThatThrownBy(() -> service.accountState(new BigDecimal("100.00"), new BigDecimal("10.00"),
|
||||||
new BigDecimal("50.00"), new BigDecimal("10.01"), LocalDate.now().plusDays(7), 1))
|
new BigDecimal("50.00"), new BigDecimal("10.01"), LocalDate.now().plusDays(7), 1))
|
||||||
@@ -165,7 +162,7 @@ class FinanceWalletBehaviorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void expiredCreditNoLongerContributesToAvailableBalance() {
|
void expiredCreditNoLongerContributesToAvailableBalance() {
|
||||||
FinanceWalletService service = service(mock(JdbcTemplate.class), "x", "y");
|
FinanceWalletService service = service(mock(JdbcTemplate.class), "x");
|
||||||
|
|
||||||
WalletAccount account = service.accountState(BigDecimal.ZERO, BigDecimal.ZERO,
|
WalletAccount account = service.accountState(BigDecimal.ZERO, BigDecimal.ZERO,
|
||||||
new BigDecimal("50.00"), new BigDecimal("20.00"), BigDecimal.ZERO,
|
new BigDecimal("50.00"), new BigDecimal("20.00"), BigDecimal.ZERO,
|
||||||
@@ -176,9 +173,9 @@ class FinanceWalletBehaviorTest {
|
|||||||
assertThat(account.availableBalance()).isEqualByComparingTo("-20.00");
|
assertThat(account.availableBalance()).isEqualByComparingTo("-20.00");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FinanceWalletService service(JdbcTemplate jdbc, String wechat, String alipay) {
|
private static FinanceWalletService service(JdbcTemplate jdbc, String qrCodeUrl) {
|
||||||
return new FinanceWalletService(jdbc, new ObjectMapper().findAndRegisterModules(),
|
return new FinanceWalletService(jdbc, new ObjectMapper().findAndRegisterModules(),
|
||||||
mock(FinanceFileClient.class), mock(FinanceDocumentNumberGenerator.class), "token", wechat, alipay,
|
mock(FinanceFileClient.class), mock(FinanceDocumentNumberGenerator.class), "token", qrCodeUrl,
|
||||||
"test", "test");
|
"test", "test");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user