package com.talentcard.server.business.payment;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import com.talentcard.server.business.BusinessIdGenerator;
import com.talentcard.server.business.DatabaseRows;
import com.talentcard.server.business.payment.huifu.HuifuDelayAccountGateway;
import com.talentcard.server.business.payment.huifu.HuifuDelayAccountGateway.HuifuDelayConfirmCommand;
import com.talentcard.server.business.payment.huifu.HuifuDelayAccountGateway.HuifuDelayConfirmRefundCommand;
import com.talentcard.server.business.payment.huifu.HuifuDelayAccountGateway.HuifuDelayConfirmation;
import com.talentcard.server.business.payment.huifu.HuifuDelayAccountGateway.HuifuDelayRevert;
import com.talentcard.server.business.payment.huifu.HuifuPaymentGatewayException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;
/**
* 延迟交易的资金确认。
*
*
商户开通延迟入账后,用 {@code delay_acct_flag=Y} 下的支付会把钱留在通道的「待确认」状态。业务规则是
* 「核销之后商户才能拿到钱」,所以核销成功时才调用交易确认接口,把这笔钱分配给收款子商户。
*
*
状态记在 {@code payment_transaction.funds_confirmation_status}:{@code NULL} 表示这笔支付没有延迟
* (支付即核销的到店消费),{@code pending} 表示钱还在通道冻着,{@code confirmed} 表示已经分配完成。
* 自动提现只认 {@code NULL} 和 {@code confirmed},因此「忘了确认」不会变成「提前把钱提走」。
*
*
撤销核销走反方向({@link #revertForOrder}):调用交易确认退款把已确认的钱退回延迟户,成功后状态
* 退回 {@code pending},商户重新核销时会再确认一次。回退期间用 {@code reverting} 占位。
*/
@Service
public class PaymentFundConfirmationService {
/**
* 资金没有走延迟入账,不需要确认。
*
*
这些常量是「返回给调用方的结论」,与 {@code payment_transaction.funds_confirmation_status} 里存的
* 状态不同名:库里存小写({@code pending}/{@code confirming}/{@code confirmed}),所以比较一律走
* {@link #isConfirmed(String)},不要直接拿常量去比字段值。
*/
public static final String NOT_DELAYED = "NOT_DELAYED";
/** 资金已分配给收款商户。 */
public static final String CONFIRMED = "CONFIRMED";
/** 支付本身还没有成功,稍后再确认。 */
public static final String NOT_SETTLED = "NOT_SETTLED";
/** 已经有一笔确认请求在途,交给定时任务收敛。 */
public static final String PROCESSING = "PROCESSING";
/** 通道明确拒绝,需要人工处理。 */
public static final String FAILED = "FAILED";
/** 找不到对应的支付记录。 */
public static final String UNKNOWN = "UNKNOWN";
/**
* 订单此刻不再是「已核销」,不能把钱放给商户。
*
*
撤销核销会把订单翻回待核销,此时钱必须留在延迟户:确认在放款前复查这个前提,
* 避免「订单已回到待核销、钱却已经打给商户」。
*/
public static final String NOT_VERIFIED = "NOT_VERIFIED";
/** 钱已退回(或本来就在)通道延迟户,撤销核销可以继续。 */
public static final String REVERTED = "REVERTED";
/** 这笔资金不需要回退:没有支付记录、支付没有走延迟入账,或钱本来就在延迟户。 */
public static final String REVERT_NOT_NEEDED = "REVERT_NOT_NEEDED";
/** 通道明确拒绝回退(例如资金已结算到银行卡、基本户余额不足),钱没有动。 */
public static final String REVERT_REJECTED = "REVERT_REJECTED";
/** 回退结果未定或仍在处理中:钱的位置不确定,撤销核销必须停在「已核销」。 */
public static final String REVERT_IN_FLIGHT = "REVERT_IN_FLIGHT";
/**
* 返回码在这条路径上语义未定:通道给了话,但我们不知道它是什么意思,需要人工核对。
*
*
和 {@link #REVERT_IN_FLIGHT} 分开是因为给商户的话不同:在途可以说「稍后重试」,
* 语义未定只能请他联系平台——它也可能是「已结算」,让商户反复重试是没有意义的。
*/
public static final String REVERT_NEEDS_REVIEW = "REVERT_NEEDS_REVIEW";
/** 回退请求根本没发出去(本地配置或 SDK 出错):钱没动,可以重试。 */
public static final String REVERT_NOT_SENT = "REVERT_NOT_SENT";
/** 找不到支付记录,或缺少定位原确认请求的键。 */
public static final String REVERT_UNKNOWN = "REVERT_UNKNOWN";
private static final DateTimeFormatter REQUEST_DATE = DateTimeFormatter.ofPattern("yyyyMMdd");
private static final Logger log = LoggerFactory.getLogger(PaymentFundConfirmationService.class);
private final JdbcTemplate jdbc;
private final TransactionTemplate transactions;
private final HuifuDelayAccountGateway gateway;
/**
* 资金侧的写入用独立事务提交。
*
*
撤销核销会在自己的事务里调用本服务,而通道调用不是事务性的:认领和结果如果跟着外层事务一起
* 回滚,通道那边已经退回延迟户的钱在本地就再也看不到了——状态还写着 {@code confirmed},钱其实已经
* 不在商户基本户。用独立事务提交,判断依据就永远留在库里。
*/
private final TransactionTemplate moneyTransactions;
public PaymentFundConfirmationService(JdbcTemplate jdbc, TransactionTemplate transactions,
HuifuDelayAccountGateway gateway) {
this.jdbc = jdbc;
this.transactions = transactions;
this.gateway = gateway;
// 注入的 TransactionTemplate 是容器里的单例,支付、退款和自动提现共用它,不能改它的传播行为,
// 否则那些服务会跟着变成 REQUIRES_NEW、外层事务被静默挂起。这里自己新建一个。
this.moneyTransactions = new TransactionTemplate(transactions.getTransactionManager());
this.moneyTransactions.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
}
/**
* 为某个订单确认资金。订单必须已经有成功的支付记录。
*
*
幂等:资金已经确认、或这笔支付本来就没有延迟时直接返回,不会重复向通道发确认请求。
*/
public String confirmForOrder(long orderId) {
List