diff --git a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/MiFaPayServerNoticeReceivePaymentCmdExe.java b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/MiFaPayServerNoticeReceivePaymentCmdExe.java index c9426c8d..c348ab6a 100644 --- a/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/MiFaPayServerNoticeReceivePaymentCmdExe.java +++ b/rc-service/rc-service-order/order-application/src/main/java/com/red/circle/order/app/command/pay/web/MiFaPayServerNoticeReceivePaymentCmdExe.java @@ -16,8 +16,9 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -import java.nio.charset.StandardCharsets; -import java.util.Objects; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.Objects; /** * MiFaPay 支付回调通知处理 @@ -94,9 +95,9 @@ public class MiFaPayServerNoticeReceivePaymentCmdExe { private String handleMysqlOrder(MiFaPayReceivePaymentNotice notice, MiFaPayReceivePaymentNotice.MiFaPayNoticeData noticeData, OrderUserPurchasePay mysqlOrder) { - orderUserPurchasePayNoticeService.addNotice( + safeAddNotice( mysqlOrder.getId(), - notice.getSign(), + buildNoticeEventId(noticeData, notice), "mifapay-" + noticeData.getOrderStatus(), notice, mysqlOrder.getUpdateUser() @@ -151,4 +152,57 @@ public class MiFaPayServerNoticeReceivePaymentCmdExe { Long mysqlOrderId = MiFaPayMysqlOrderSupport.parseOrderId(orderId); return mysqlOrderId == null ? null : orderUserPurchasePayService.getById(mysqlOrderId); } -} + + private void safeAddNotice(Long purchasePayId, String eventId, String noticeType, Object noticeData, + Long operationUserId) { + try { + orderUserPurchasePayNoticeService.addNotice( + purchasePayId, eventId, noticeType, noticeData, operationUserId); + } catch (Exception e) { + log.error("MiFaPay回调通知落库失败,继续执行支付主流程: purchasePayId={}, eventId={}, noticeType={}", + purchasePayId, eventId, noticeType, e); + } + } + + private String buildNoticeEventId(MiFaPayReceivePaymentNotice.MiFaPayNoticeData noticeData, + MiFaPayReceivePaymentNotice notice) { + String orderId = noticeData.getOrderId(); + String bankOrderNo = noticeData.getBankOrderNo(); + String mbOrderId = noticeData.getMbOrderId(); + if (StringUtils.isNotBlank(orderId) && StringUtils.isNotBlank(bankOrderNo)) { + return "mifapay:" + orderId + ":" + bankOrderNo; + } + if (StringUtils.isNotBlank(orderId) && StringUtils.isNotBlank(mbOrderId)) { + return "mifapay:" + orderId + ":" + mbOrderId; + } + if (StringUtils.isNotBlank(orderId)) { + return "mifapay:" + orderId; + } + if (StringUtils.isNotBlank(bankOrderNo)) { + return "mifapay:bank:" + bankOrderNo; + } + if (StringUtils.isNotBlank(mbOrderId)) { + return "mifapay:mb:" + mbOrderId; + } + return "mifapay:sign:" + sha256Hex(Objects.toString(notice.getSign(), "")); + } + + private String sha256Hex(String value) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(value.getBytes(StandardCharsets.UTF_8)); + StringBuilder hex = new StringBuilder(hash.length * 2); + for (byte b : hash) { + String item = Integer.toHexString(b & 0xff); + if (item.length() == 1) { + hex.append('0'); + } + hex.append(item); + } + return hex.toString(); + } catch (Exception e) { + log.error("MiFaPay回调sign生成SHA-256失败", e); + return "hash-error"; + } + } +} diff --git a/rc-service/rc-service-order/order-domain/src/main/java/com/red/circle/order/domain/order/MiFaPayReceivePaymentNotice.java b/rc-service/rc-service-order/order-domain/src/main/java/com/red/circle/order/domain/order/MiFaPayReceivePaymentNotice.java index a606fafd..34a07c09 100644 --- a/rc-service/rc-service-order/order-domain/src/main/java/com/red/circle/order/domain/order/MiFaPayReceivePaymentNotice.java +++ b/rc-service/rc-service-order/order-domain/src/main/java/com/red/circle/order/domain/order/MiFaPayReceivePaymentNotice.java @@ -69,6 +69,11 @@ public class MiFaPayReceivePaymentNotice implements Serializable { */ private String mbOrderId; + /** + * 银行或通道订单号(平台回调返回,用于幂等事件标识) + */ + private String bankOrderNo; + /** * 商户唯一订单号(与下单时传入的orderId一致,用于匹配商户本地订单) */ diff --git a/sql/20260420_mifapay_order_mysql.sql b/sql/20260420_mifapay_order_mysql.sql index facf4d1a..edc9c7c2 100644 --- a/sql/20260420_mifapay_order_mysql.sql +++ b/sql/20260420_mifapay_order_mysql.sql @@ -52,7 +52,7 @@ WHERE `receipt_type` = ''; CREATE TABLE IF NOT EXISTS `order_user_purchase_pay_notice` ( `id` bigint NOT NULL COMMENT '主键ID', `purchase_pay_id` bigint NOT NULL COMMENT '支付预订单ID', - `event_id` varchar(128) NOT NULL DEFAULT '' COMMENT '事件标识', + `event_id` varchar(512) NOT NULL DEFAULT '' COMMENT '事件标识', `notice_type` varchar(64) NOT NULL DEFAULT '' COMMENT '通知类型', `notice_data` longtext COMMENT '通知内容(JSON)', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', diff --git a/sql/20260506_mifapay_notice_event_id_512.sql b/sql/20260506_mifapay_notice_event_id_512.sql new file mode 100644 index 00000000..db97ce5f --- /dev/null +++ b/sql/20260506_mifapay_notice_event_id_512.sql @@ -0,0 +1,19 @@ +SET @current_schema = DATABASE(); + +SELECT CHARACTER_MAXIMUM_LENGTH +INTO @notice_event_id_length +FROM INFORMATION_SCHEMA.COLUMNS +WHERE TABLE_SCHEMA = @current_schema + AND TABLE_NAME = 'order_user_purchase_pay_notice' + AND COLUMN_NAME = 'event_id' +LIMIT 1; + +SET @ddl = IF( + @notice_event_id_length IS NOT NULL AND @notice_event_id_length < 512, + 'ALTER TABLE `order_user_purchase_pay_notice` MODIFY COLUMN `event_id` varchar(512) NOT NULL DEFAULT '''' COMMENT ''事件标识''', + 'SELECT 1' +); + +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt;