package com.talentcard.server.business.payment;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.alibaba.fastjson.JSON;
import com.talentcard.server.business.DatabaseRows;
import com.talentcard.server.business.order.OrderFundBoundaryService;
import com.talentcard.server.business.payment.PaymentSettlementService.ProviderPaymentFact;
import com.talentcard.server.business.payment.PaymentSettlementService.SettlementOutcome;
import com.talentcard.server.business.payment.huifu.HuifuPayload;
import com.talentcard.server.business.payment.huifu.HuifuPayload.TransStatus;
import com.talentcard.server.business.payment.huifu.HuifuSignatureService;
import com.talentcard.server.common.BusinessException;
import com.talentcard.server.common.ErrorCode;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* Handles Huifu asynchronous notifications.
*
*
The signature is verified first; only then is the payload interpreted. A successful payment
* notification is handed to {@link PaymentSettlementService}, which compares the request number,
* merchant number and amount with the order's fixed collecting sub-merchant before any state
* changes. Duplicated notifications for an already processed request are acknowledged without
* touching business state.
*/
@Service
public class HuifuPaymentCallbackService {
private static final String PROVIDER = "HUIFU";
/** Event states that already describe a final decision for this exact payload. */
private static final Set TERMINAL_EVENT_STATUSES = Set.of(
"PROCESSED", "PAYMENT_CLOSED", "UNKNOWN_STATUS",
"MERCHANT_MISMATCH", "MERCHANT_UNVERIFIED", "AMOUNT_MISMATCH", "COLLECTOR_UNKNOWN");
private final JdbcTemplate jdbc;
private final HuifuSignatureService signatureService;
private final PaymentSettlementService settlementService;
private final OrderFundBoundaryService orderFundBoundary;
public HuifuPaymentCallbackService(JdbcTemplate jdbc, HuifuSignatureService signatureService, PaymentSettlementService settlementService, OrderFundBoundaryService orderFundBoundary) {
this.jdbc = jdbc;
this.signatureService = signatureService;
this.settlementService = settlementService;
this.orderFundBoundary = orderFundBoundary;
}
@Transactional
public String handle(String responseData, String signature) {
if (!signatureService.verifyCallbackPayload(responseData, signature)) {
throw new BusinessException(ErrorCode.FORBIDDEN, "汇付回调验签失败");
}
Map envelope = parse(responseData);
Map data = HuifuPayload.businessData(envelope);
String requestNo = HuifuPayload.firstNonBlank(HuifuPayload.value(data, "req_seq_id"), HuifuPayload.value(envelope, "req_seq_id"));
if (requestNo.isBlank()) throw new BusinessException(ErrorCode.BAD_REQUEST, "汇付回调缺少请求流水号");
String payloadHash = sha256(responseData);
boolean alreadyHandled = alreadyHandled(requestNo, payloadHash);
upsertEvent(requestNo, payloadHash, "RECEIVED");
if (alreadyHandled) return acknowledgement(requestNo);
if (handleRefund(requestNo, data)) {
return acknowledgement(requestNo);
}
String transStatus = HuifuPayload.value(data, "trans_stat", "transStat");
if (HuifuPayload.status(transStatus) == TransStatus.UNKNOWN) {
updateEvent(requestNo, "UNKNOWN_STATUS");
return acknowledgement(requestNo);
}
if (!transactionExists(requestNo)) {
updateEvent(requestNo, "UNMATCHED");
return acknowledgement(requestNo);
}
if (!HuifuPayload.paid(transStatus, HuifuPayload.code(data))) {
// The provider did not confirm money: keep the payment pending for the query path.
jdbc.update("""
update payment_transaction
set provider_response_code = ?, provider_response_message = ?
where provider = ? and payment_no = ? and status in ('CREATED', 'CREATE_FAILED', 'PREPAY_CREATED', 'CREATE_UNKNOWN')
""", HuifuPayload.code(data), HuifuPayload.message(data), PROVIDER, requestNo);
updateEvent(requestNo, "PENDING_QUERY");
return acknowledgement(requestNo);
}
SettlementOutcome outcome = settlementService.settle(new ProviderPaymentFact(
requestNo,
HuifuPayload.value(data, "huifu_id", "huifuId"),
HuifuPayload.amountInCent(HuifuPayload.value(data, "trans_amt", "transAmt")),
providerTransactionNo(data),
HuifuPayload.code(data),
HuifuPayload.message(data),
"CALLBACK"));
updateEvent(requestNo, eventStatus(outcome));
return acknowledgement(requestNo);
}
private boolean handleRefund(String refundNo, Map data) {
List