|
|
@@ -7,6 +7,7 @@ import org.springframework.beans.factory.ObjectProvider;
|
|
|
import org.springframework.beans.factory.annotation.Qualifier;
|
|
|
import org.springframework.jdbc.core.JdbcTemplate;
|
|
|
import org.springframework.stereotype.Repository;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
import java.util.List;
|
|
|
|
|
|
@@ -19,6 +20,7 @@ public class BankingPaymentTrackingRepository {
|
|
|
public static final String TRACKING_STATUS_PENDING = "PENDING";
|
|
|
public static final String TRACKING_STATUS_RECEIPT_PENDING = "RECEIPT_PENDING";
|
|
|
public static final String TRACKING_STATUS_COMPLETED = "COMPLETED";
|
|
|
+ private static final int MAX_PAYMENT_ATTEMPT_NO = 9999;
|
|
|
|
|
|
private final ObjectProvider<JdbcTemplate> jdbcTemplateProvider;
|
|
|
private final BankingPaymentTrackingProperties properties;
|
|
|
@@ -38,26 +40,46 @@ public class BankingPaymentTrackingRepository {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 制单成功后写入待补偿记录;同一支付单号重复提交时保留原有处理状态。
|
|
|
+ * 为同一原单据号分配下一次银企支付流水号并预先保存跟踪记录。
|
|
|
+ * 数据库锁和唯一索引共同保证多个应用实例并发提交时不会获得相同的流水号。
|
|
|
+ *
|
|
|
+ * @param request 包含原始单据号和支付信息的制单请求
|
|
|
+ * @return 发送至银企接口的唯一支付流水号
|
|
|
*/
|
|
|
- public void saveSubmittedPayment(SinglePaymentRequest request) {
|
|
|
+ @Transactional
|
|
|
+ public String reservePaymentAttempt(SinglePaymentRequest request) {
|
|
|
+ String sourceBusinessTransNo = requireText(request.getBusinessTransNo(), "原单据号不能为空");
|
|
|
+ if (sourceBusinessTransNo.length() > 124) {
|
|
|
+ throw new IllegalArgumentException("原单据号长度不能超过 124 个字符");
|
|
|
+ }
|
|
|
+ Integer latestAttemptNo = findLatestAttemptNoForUpdate(sourceBusinessTransNo);
|
|
|
+ int paymentAttemptNo = latestAttemptNo == null ? 1 : latestAttemptNo.intValue() + 1;
|
|
|
+ if (paymentAttemptNo > MAX_PAYMENT_ATTEMPT_NO) {
|
|
|
+ throw new IllegalStateException("同一单据号的支付提交次数不能超过 9999 次");
|
|
|
+ }
|
|
|
+ String businessTransNo = sourceBusinessTransNo + String.format("%04d", Integer.valueOf(paymentAttemptNo));
|
|
|
String sql = "INSERT INTO " + tableName() + " "
|
|
|
- + "(business_trans_no, pay_amount, payment_account_no, customerAccountNo, customerName, "
|
|
|
- + "customerBank, remark, payment_status, tracking_status, form_id, form_instance_id) "
|
|
|
- + "VALUES (?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?) "
|
|
|
- + "ON DUPLICATE KEY UPDATE pay_amount = VALUES(pay_amount), "
|
|
|
- + "payment_account_no = VALUES(payment_account_no), "
|
|
|
- + "customerAccountNo = VALUES(customerAccountNo), "
|
|
|
- + "customerName = VALUES(customerName), "
|
|
|
- + "customerBank = VALUES(customerBank), "
|
|
|
- + "remark = VALUES(remark), "
|
|
|
- + "form_id = COALESCE(VALUES(form_id), form_id), "
|
|
|
- + "form_instance_id = COALESCE(VALUES(form_instance_id), form_instance_id), updated_at = CURRENT_TIMESTAMP";
|
|
|
- jdbcTemplate().update(sql, request.getBusinessTransNo(), request.getPayAmount(),
|
|
|
+ + "(source_business_trans_no, payment_attempt_no, business_trans_no, pay_amount, payment_account_no, "
|
|
|
+ + "customerAccountNo, customerName, customerBank, remark, payment_status, tracking_status, form_id, "
|
|
|
+ + "form_instance_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?)";
|
|
|
+ jdbcTemplate().update(sql, sourceBusinessTransNo, Integer.valueOf(paymentAttemptNo), businessTransNo,
|
|
|
+ request.getPayAmount(),
|
|
|
emptyToNull(request.getFromAccountNo()), emptyToNull(request.getToAccountNo()),
|
|
|
emptyToNull(request.getToAccountName()), emptyToNull(request.getToBankName()),
|
|
|
emptyToNull(resolveRemark(request)), TRACKING_STATUS_PENDING,
|
|
|
emptyToNull(request.getFormID()), emptyToNull(request.getFormInstanceId()));
|
|
|
+ return businessTransNo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 标记银企接口已明确拒绝制单的记录,避免定时任务继续查询不存在的支付单。
|
|
|
+ *
|
|
|
+ * @param businessTransNo 已分配的银企支付流水号
|
|
|
+ */
|
|
|
+ public void markSubmissionRejected(String businessTransNo) {
|
|
|
+ String sql = "UPDATE " + tableName() + " SET payment_status = 'SUBMISSION_REJECTED', tracking_status = ?, "
|
|
|
+ + "updated_at = CURRENT_TIMESTAMP WHERE business_trans_no = ?";
|
|
|
+ jdbcTemplate().update(sql, TRACKING_STATUS_COMPLETED, businessTransNo);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -125,6 +147,18 @@ public class BankingPaymentTrackingRepository {
|
|
|
jdbcTemplate().update(sql, emptyToNull(paymentStatus), TRACKING_STATUS_COMPLETED, businessTransNo);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 宜搭表单实例已删除时结束补偿任务,避免持续查询支付状态、下载回单和回写不存在的数据。
|
|
|
+ * 保留已记录的银企支付状态,供后续人工核查。
|
|
|
+ *
|
|
|
+ * @param businessTransNo 银企支付流水号
|
|
|
+ */
|
|
|
+ public void markYidaFormDataDeleted(String businessTransNo) {
|
|
|
+ String sql = "UPDATE " + tableName() + " SET tracking_status = ?, updated_at = CURRENT_TIMESTAMP "
|
|
|
+ + "WHERE business_trans_no = ?";
|
|
|
+ jdbcTemplate().update(sql, TRACKING_STATUS_COMPLETED, businessTransNo);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 查询异常时更新时间,记录会在下一轮任务继续重试。
|
|
|
*/
|
|
|
@@ -164,4 +198,18 @@ public class BankingPaymentTrackingRepository {
|
|
|
return request.getPurpose();
|
|
|
}
|
|
|
|
|
|
+ private Integer findLatestAttemptNoForUpdate(String sourceBusinessTransNo) {
|
|
|
+ String sql = "SELECT payment_attempt_no FROM " + tableName()
|
|
|
+ + " WHERE source_business_trans_no = ? ORDER BY payment_attempt_no DESC LIMIT 1 FOR UPDATE";
|
|
|
+ return jdbcTemplate().query(sql, preparedStatement -> preparedStatement.setString(1, sourceBusinessTransNo),
|
|
|
+ resultSet -> resultSet.next() ? Integer.valueOf(resultSet.getInt("payment_attempt_no")) : null);
|
|
|
+ }
|
|
|
+
|
|
|
+ private String requireText(String value, String message) {
|
|
|
+ if (value == null || value.trim().isEmpty()) {
|
|
|
+ throw new IllegalArgumentException(message);
|
|
|
+ }
|
|
|
+ return value.trim();
|
|
|
+ }
|
|
|
+
|
|
|
}
|