Commit 2ac6fcb6 by lijinqi

支付回调接口V0.1

parent 842a425c
......@@ -22,6 +22,9 @@ import java.time.format.DateTimeFormatter;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.luhu.computility.module.pay.framework.pay.core.client.impl.wpgj.WpgjPayProperties;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
/**
* 算力资源模块 - WPGJ旺铺聚合支付回调Controller
......@@ -78,6 +81,124 @@ public class ComputeWpgjPayController {
}
}
@PostMapping("/new-notify")
@PermitAll
@Operation(summary = "WPGJ支付异步回调通知(新) - 按文档通用验签")
public CommonResult<WpgjPayNotifyRespDTO> notifyWpgjPayNew(@RequestBody WpgjPayNotifyDTO notifyDTO) {
log.info("[notifyWpgjPayNew][Compute] 收到WPGJ支付回调(新): {}", "new-notify进来了进来了————————————————————————————————————————————————————————————————————————————————————————————————");
WpgjPayNotifyRespDTO response = new WpgjPayNotifyRespDTO();
try {
log.info("[notifyWpgjPayNew][Compute] 收到WPGJ支付回调(新): {}", JsonUtils.toJsonString(notifyDTO));
// 1. 验证签名(对所有出现的非空字段,按 ASCII 升序拼接,排除 sign)
if (!verifyWpgjSignature(toPayloadMap(notifyDTO))) {
log.error("[notifyWpgjPayNew][Compute] WPGJ回调签名验证失败");
response.setCode("99");
response.setMsg("签名验证失败");
response.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
return CommonResult.success(response);
}
String merOrderId = notifyDTO.getMerOrderId();
String orderStatus = notifyDTO.getOrderStatus();
log.info("[notifyWpgjPayNew] 处理WPGJ支付结果,商户订单号: {}, 订单状态: {}", merOrderId, orderStatus);
// 2. 幂等处理:根据订单状态更新(与老接口保持一致逻辑)
if (WpgjOrderStatusEnum.isSuccess(orderStatus)) {
resourceOrderService.updateOrderPaidByWpgj(Long.parseLong(merOrderId), notifyDTO);
log.info("[notifyWpgjPayNew] 支付成功处理完成,商户订单号: {}", merOrderId);
} else if (WpgjOrderStatusEnum.isFailedOrClosed(orderStatus)) {
resourceOrderService.updateOrderFailedByWpgj(Long.parseLong(merOrderId), notifyDTO);
log.info("[notifyWpgjPayNew] 支付失败/关闭处理完成,商户订单号: {}", merOrderId);
} else {
log.info("[notifyWpgjPayNew] 订单状态无需处理,商户订单号: {}, 状态: {}", merOrderId, orderStatus);
}
// 3. 返回成功应答
response.setCode("00");
response.setMsg("成功");
response.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
return CommonResult.success(response);
} catch (Exception e) {
log.error("[notifyWpgjPayNew][Compute] WPGJ支付回调处理失败", e);
response.setCode("99");
response.setMsg("处理失败");
response.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
return CommonResult.success(response);
}
}
/**
* Map 形式的回调请求验签:对所有出现的非空字段(排除 sign)进行 ASCII 排序拼接后 + key,再 MD5(UTF-8) 大写
*/
private boolean verifyWpgjSignature(Map<String, Object> payload) {
try {
Object signObj = payload.get("sign");
String originSign = signObj == null ? null : String.valueOf(signObj);
if (StrUtil.isBlank(originSign)) {
log.warn("[verifyWpgjSignature][Compute] 回调缺少 sign 字段");
return false;
}
// 过滤掉 sign、自身为空的字段,按 ASCII 升序
TreeMap<String, String> sorted = new TreeMap<>();
for (Map.Entry<String, Object> e : payload.entrySet()) {
String k = e.getKey();
if ("sign".equals(k)) {
continue;
}
String v = e.getValue() == null ? null : String.valueOf(e.getValue());
if (StrUtil.isBlank(v)) {
continue;
}
sorted.put(k, v);
}
String dataStr = sorted.entrySet().stream()
.map(en -> en.getKey() + "=" + en.getValue())
.collect(Collectors.joining("&"));
String signStr = dataStr + "&key=" + wpgjPayProperties.getSignKey();
String calculatedSign = DigestUtil.md5Hex(signStr).toUpperCase();
log.info("[verifyWpgjSignature][Compute-New] 待签名字符串: {}", dataStr);
log.info("[verifyWpgjSignature][Compute-New] 加签前(带key): {}", signStr);
log.info("[verifyWpgjSignature][Compute-New] 计算签名: {},原始签名: {}", calculatedSign, originSign);
return calculatedSign.equalsIgnoreCase(originSign);
} catch (Exception e) {
log.error("[verifyWpgjSignature][Compute-New] 验签异常", e);
return false;
}
}
/**
* 将 DTO 转换为 Map(以供应商字段名 snake_case 作为 key),便于统一验签
*/
private Map<String, Object> toPayloadMap(WpgjPayNotifyDTO dto) {
TreeMap<String, Object> map = new TreeMap<>();
map.put("device_no", dto.getDeviceNo());
map.put("mer_no", dto.getMerNo());
map.put("mer_code", dto.getMerCode());
map.put("payway_code", dto.getPaywayCode());
map.put("order_id", dto.getOrderId());
map.put("mer_order_id", dto.getMerOrderId());
map.put("gateway_mer_order_id", dto.getGatewayMerOrderId());
map.put("order_time", dto.getOrderTime());
map.put("order_amt", dto.getOrderAmt());
map.put("order_status", dto.getOrderStatus());
map.put("trade_no", dto.getTradeNo());
map.put("trade_time", dto.getTradeTime());
map.put("order_title", dto.getOrderTitle());
map.put("fee", dto.getFee());
map.put("act_amt", dto.getActAmt());
map.put("buyer_id", dto.getBuyerId());
map.put("trade_top_no", dto.getTradeTopNo());
map.put("card_type", dto.getCardType());
map.put("sign", dto.getSign());
return map;
}
/**
* 处理WPGJ支付结果
*/
......@@ -112,69 +233,7 @@ public class ComputeWpgjPayController {
* 验证WPGJ回调签名
*/
private boolean verifyWpgjSignature(WpgjPayNotifyDTO notifyDTO) {
try {
// 1. 构建待签名字符串
StringBuilder sb = new StringBuilder();
// 按照字段名ASCII码排序拼接
sb.append("device_no=").append(notifyDTO.getDeviceNo());
sb.append("&mer_no=").append(notifyDTO.getMerNo());
sb.append("&payway_code=").append(notifyDTO.getPaywayCode());
sb.append("&order_id=").append(notifyDTO.getOrderId());
sb.append("&mer_order_id=").append(notifyDTO.getMerOrderId());
sb.append("&order_time=").append(notifyDTO.getOrderTime());
sb.append("&order_amt=").append(notifyDTO.getOrderAmt());
sb.append("&order_status=").append(notifyDTO.getOrderStatus());
// 可选字段(非空才加入)
if (StrUtil.isNotBlank(notifyDTO.getMerCode())) {
sb.append("&mer_code=").append(notifyDTO.getMerCode());
}
if (StrUtil.isNotBlank(notifyDTO.getGatewayMerOrderId())) {
sb.append("&gateway_mer_order_id=").append(notifyDTO.getGatewayMerOrderId());
}
if (StrUtil.isNotBlank(notifyDTO.getTradeNo())) {
sb.append("&trade_no=").append(notifyDTO.getTradeNo());
}
if (StrUtil.isNotBlank(notifyDTO.getTradeTime())) {
sb.append("&trade_time=").append(notifyDTO.getTradeTime());
}
if (StrUtil.isNotBlank(notifyDTO.getOrderTitle())) {
sb.append("&order_title=").append(notifyDTO.getOrderTitle());
}
if (StrUtil.isNotBlank(notifyDTO.getFee())) {
sb.append("&fee=").append(notifyDTO.getFee());
}
if (StrUtil.isNotBlank(notifyDTO.getActAmt())) {
sb.append("&act_amt=").append(notifyDTO.getActAmt());
}
if (StrUtil.isNotBlank(notifyDTO.getBuyerId())) {
sb.append("&buyer_id=").append(notifyDTO.getBuyerId());
}
if (StrUtil.isNotBlank(notifyDTO.getTradeTopNo())) {
sb.append("&trade_top_no=").append(notifyDTO.getTradeTopNo());
}
if (StrUtil.isNotBlank(notifyDTO.getCardType())) {
sb.append("&card_type=").append(notifyDTO.getCardType());
}
// 2. 添加key
sb.append("&key=").append(wpgjPayProperties.getSignKey());
// 3. 计算MD5签名
String signStr = sb.toString();
String calculatedSign = DigestUtil.md5Hex(signStr).toUpperCase();
log.info("[verifyWpgjSignature][Compute] 待签名字符串: {}", signStr);
log.info("[verifyWpgjSignature][Compute] 计算签名: {}", calculatedSign);
log.info("[verifyWpgjSignature][Compute] 原始签名: {}", notifyDTO.getSign());
// 4. 验证签名
return calculatedSign.equals(notifyDTO.getSign());
} catch (Exception e) {
log.error("[verifyWpgjSignature][Compute] WPGJ签名验证异常", e);
return false;
}
// 复用 Map 验签,确保与文档一致(ASCII 排序 + key)
return verifyWpgjSignature(toPayloadMap(notifyDTO));
}
}
\ No newline at end of file
}
package com.luhu.computility.module.pay.controller.admin.notify.vo;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
......@@ -16,68 +18,88 @@ public class WpgjPayNotifyDTO {
@Schema(description = "旺铺平台唯一设备SN号", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "设备编号不能为空")
@JsonProperty("device_no")
private String deviceNo;
@Schema(description = "内部商户号", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "商户号不能为空")
@JsonProperty("mer_no")
private String merNo;
@Schema(description = "商户代码", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "商户代码不能为空")
@JsonProperty("mer_code")
private String merCode;
@Schema(description = "支付通道代码", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "支付通道代码不能为空")
@JsonProperty("payway_code")
private String paywayCode;
@Schema(description = "旺铺订单号", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "旺铺订单号不能为空")
@JsonProperty("order_id")
private String orderId;
@Schema(description = "商户订单id,系统唯一", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "商户订单号不能为空")
@JsonProperty("mer_order_id")
private String merOrderId;
@Schema(description = "网关商户订单号")
@JsonProperty("gateway_mer_order_id")
private String gatewayMerOrderId;
@Schema(description = "下单时间", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "下单时间不能为空")
@JsonProperty("order_time")
private String orderTime;
@Schema(description = "订单金额", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "订单金额不能为空")
@JsonProperty("order_amt")
private String orderAmt;
@Schema(description = "订单状态", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "订单状态不能为空")
@JsonProperty("order_status")
private String orderStatus;
@Schema(description = "通道交易流水号")
@JsonProperty("trade_no")
private String tradeNo;
@Schema(description = "交易时间,支付成功返回")
@JsonProperty("trade_time")
private String tradeTime;
@Schema(description = "订单标题")
@JsonProperty("order_title")
private String orderTitle;
@Schema(description = "订单手续费")
@JsonProperty("fee")
private String fee;
@Schema(description = "结算金额")
@JsonProperty("act_amt")
private String actAmt;
@Schema(description = "买家用户号")
@JsonProperty("buyer_id")
@JsonAlias("buyer_user_id")
private String buyerId;
@Schema(description = "对应微信、支付宝小票上交易单号")
@JsonProperty("trade_top_no")
private String tradeTopNo;
@Schema(description = "交易账户类型 C-贷记卡,D-借记卡,U-未知")
@JsonProperty("card_type")
private String cardType;
@Schema(description = "报文签名值", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "签名不能为空")
@JsonProperty("sign")
private String sign;
}
\ No newline at end of file
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment