Commit 842a425c by Jony.L

新支付功能测试6.0,继续回调测试

parent fa76472d
package com.luhu.computility.module.compute.controller.admin.notify;
import cn.hutool.core.util.StrUtil;
import com.luhu.computility.framework.common.pojo.CommonResult;
import com.luhu.computility.framework.common.util.json.JsonUtils;
import com.luhu.computility.module.pay.controller.admin.notify.vo.WpgjPayNotifyDTO;
import com.luhu.computility.module.pay.controller.admin.notify.vo.WpgjPayNotifyRespDTO;
import com.luhu.computility.module.compute.service.resourceorder.ResourceOrderService;
import com.luhu.computility.module.pay.enums.WpgjOrderStatusEnum;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestBody;
import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import java.time.LocalDateTime;
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;
/**
* 算力资源模块 - WPGJ旺铺聚合支付回调Controller
*
* 独立处理WPGJ支付回调,直接更新算力资源订单状态
*
* @author jony
*/
@Tag(name = "管理后台 - 算力资源WPGJ旺铺聚合支付回调")
@RestController
@RequestMapping("/compute/wpgj")
@Slf4j
public class ComputeWpgjPayController {
@Resource
private ResourceOrderService resourceOrderService;
@Resource
private WpgjPayProperties wpgjPayProperties;
@PostMapping("/notify")
@PermitAll
@Operation(summary = "WPGJ支付异步回调通知 - 算力资源模块")
public CommonResult<WpgjPayNotifyRespDTO> notifyWpgjPay(@RequestBody WpgjPayNotifyDTO notifyDTO) {
WpgjPayNotifyRespDTO response = new WpgjPayNotifyRespDTO();
try {
log.info("[notifyWpgjPay][Compute] 收到WPGJ支付回调: {}", JsonUtils.toJsonString(notifyDTO));
// 1. 验证签名
if (!verifyWpgjSignature(notifyDTO)) {
log.error("[notifyWpgjPay][Compute] WPGJ回调签名验证失败");
response.setCode("99");
response.setMsg("签名验证失败");
response.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
return CommonResult.success(response);
}
// 2. 处理支付结果通知
handleWpgjPaymentResult(notifyDTO);
log.info("[notifyWpgjPay][Compute] WPGJ支付回调处理完成");
response.setCode("00");
response.setMsg("成功");
response.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
return CommonResult.success(response);
} catch (Exception e) {
log.error("[notifyWpgjPay][Compute] WPGJ支付回调处理失败", e);
response.setCode("99");
response.setMsg("处理失败");
response.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
return CommonResult.success(response);
}
}
/**
* 处理WPGJ支付结果
*/
private void handleWpgjPaymentResult(WpgjPayNotifyDTO notifyDTO) {
try {
String merOrderId = notifyDTO.getMerOrderId();
String orderStatus = notifyDTO.getOrderStatus();
log.info("[handleWpgjPaymentResult] 处理WPGJ支付结果,商户订单号: {}, 订单状态: {}", merOrderId, orderStatus);
// 根据WPGJ订单状态更新算力资源订单
if (WpgjOrderStatusEnum.isSuccess(orderStatus)) {
// 支付成功
resourceOrderService.updateOrderPaidByWpgj(Long.parseLong(merOrderId), notifyDTO);
log.info("[handleWpgjPaymentResult] 算力资源订单支付成功处理完成,商户订单号: {}", merOrderId);
} else if (WpgjOrderStatusEnum.isFailedOrClosed(orderStatus)) {
// 支付失败或关闭
resourceOrderService.updateOrderFailedByWpgj(Long.parseLong(merOrderId), notifyDTO);
log.info("[handleWpgjPaymentResult] 算力资源订单支付失败处理完成,商户订单号: {}", merOrderId);
} else {
// 其他状态(处理中等),暂不处理
log.info("[handleWpgjPaymentResult] WPGJ订单状态无需处理,商户订单号: {}, 状态: {}", merOrderId, orderStatus);
}
} catch (Exception e) {
log.error("[handleWpgjPaymentResult] 处理WPGJ支付结果异常", e);
throw e;
}
}
/**
* 验证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;
}
}
}
\ No newline at end of file
...@@ -6,11 +6,7 @@ import com.luhu.computility.framework.common.exception.ServiceException; ...@@ -6,11 +6,7 @@ import com.luhu.computility.framework.common.exception.ServiceException;
import com.luhu.computility.framework.common.pojo.CommonResult; import com.luhu.computility.framework.common.pojo.CommonResult;
import com.luhu.computility.framework.common.pojo.PageResult; import com.luhu.computility.framework.common.pojo.PageResult;
import com.luhu.computility.framework.common.util.json.JsonUtils; import com.luhu.computility.framework.common.util.json.JsonUtils;
import com.luhu.computility.module.compute.controller.admin.resourceorder.vo.WpgjPayNotifyDTO;
import com.luhu.computility.module.compute.controller.admin.resourceorder.vo.WpgjPayNotifyRespDTO;
import com.luhu.computility.module.compute.enums.WpgjOrderStatusEnum;
import com.luhu.computility.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO; import com.luhu.computility.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO;
import com.luhu.computility.module.pay.framework.pay.core.client.impl.wpgj.WpgjPayProperties;
import com.luhu.computility.module.pay.service.order.PayOrderService; import com.luhu.computility.module.pay.service.order.PayOrderService;
import com.luhu.computility.module.compute.controller.admin.resourceorder.vo.*; import com.luhu.computility.module.compute.controller.admin.resourceorder.vo.*;
import com.luhu.computility.module.compute.enums.ResourceOrderInvoiceStatus; import com.luhu.computility.module.compute.enums.ResourceOrderInvoiceStatus;
...@@ -57,8 +53,6 @@ public class ResourceOrderController { ...@@ -57,8 +53,6 @@ public class ResourceOrderController {
@Resource @Resource
private PayOrderService payOrderService; private PayOrderService payOrderService;
@Resource
private WpgjPayProperties wpgjPayProperties;
/** /**
* 内部支付任务回调 * 内部支付任务回调
...@@ -72,148 +66,7 @@ public class ResourceOrderController { ...@@ -72,148 +66,7 @@ public class ResourceOrderController {
return success(true); return success(true);
} }
/**
* WPGJ旺铺聚合支付回调通知
*/
@PostMapping("/pay/wpgj-notify")
@PermitAll
@TenantIgnore
@Operation(summary = "WPGJ支付异步回调通知")
public WpgjPayNotifyRespDTO notifyWpgjPay(@RequestBody WpgjPayNotifyDTO notifyDTO) {
WpgjPayNotifyRespDTO response = new WpgjPayNotifyRespDTO();
try {
log.info("[notifyWpgjPay] 收到WPGJ支付回调: {}", JsonUtils.toJsonString(notifyDTO));
// 1. 验证签名
if (!verifyWpgjSignature(notifyDTO)) {
log.error("[notifyWpgjPay] WPGJ回调签名验证失败");
response.setCode("99");
response.setMsg("签名验证失败");
response.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
return response;
}
// 2. 解析回调数据,构造PayOrderRespDTO
PayOrderRespDTO notify = parseWpgjNotifyData(notifyDTO);
// 3. 处理支付结果通知
// 由于WPGJ是特殊渠道,直接调用内部处理方法
payOrderService.notifyOrder(-1L, notify); // 使用-1表示WPGJ特殊渠道
log.info("[notifyWpgjPay] WPGJ支付回调处理完成");
response.setCode("00");
response.setMsg("成功");
response.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
return response;
} catch (Exception e) {
log.error("[notifyWpgjPay] WPGJ支付回调处理失败", e);
response.setCode("99");
response.setMsg("处理失败");
response.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
return response;
}
}
/**
* 验证WPGJ回调签名
*/
private boolean verifyWpgjSignature(WpgjPayNotifyDTO notifyDTO) {
try {
// 1. 构建待签名字符串 - 按照WPGJ文档要求的ASCII码排序
StringBuilder sb = new StringBuilder();
// 严格按照ASCII码排序的所有参数
appendParam(sb, "act_amt", notifyDTO.getActAmt());
appendParam(sb, "buyer_id", notifyDTO.getBuyerId());
appendParam(sb, "card_type", notifyDTO.getCardType());
appendParam(sb, "device_no", notifyDTO.getDeviceNo());
appendParam(sb, "fee", notifyDTO.getFee());
appendParam(sb, "gateway_mer_order_id", notifyDTO.getGatewayMerOrderId());
appendParam(sb, "mer_code", notifyDTO.getMerCode());
appendParam(sb, "mer_no", notifyDTO.getMerNo());
appendParam(sb, "mer_order_id", notifyDTO.getMerOrderId());
appendParam(sb, "order_amt", notifyDTO.getOrderAmt());
appendParam(sb, "order_id", notifyDTO.getOrderId());
appendParam(sb, "order_status", notifyDTO.getOrderStatus());
appendParam(sb, "order_time", notifyDTO.getOrderTime());
appendParam(sb, "order_title", notifyDTO.getOrderTitle());
appendParam(sb, "payway_code", notifyDTO.getPaywayCode());
appendParam(sb, "trade_no", notifyDTO.getTradeNo());
appendParam(sb, "trade_time", notifyDTO.getTradeTime());
appendParam(sb, "trade_top_no", notifyDTO.getTradeTopNo());
// 3. 添加key
String signKey = wpgjPayProperties.getSignKey(); // 签名密钥
sb.append("&key=").append(signKey);
// 4. 计算MD5签名
String signStr = sb.toString();
String calculatedSign = cn.hutool.crypto.digest.DigestUtil.md5Hex(signStr).toUpperCase();
log.info("[verifyWpgjSignature] 待签名字符串: {}", signStr);
log.info("[verifyWpgjSignature] 计算签名: {}", calculatedSign);
log.info("[verifyWpgjSignature] 原始签名: {}", notifyDTO.getSign());
// 5. 验证签名
return calculatedSign.equals(notifyDTO.getSign());
} catch (Exception e) {
log.error("[verifyWpgjSignature] 签名验证异常", e);
return false;
}
}
/**
* 追加参数到签名字符串
*/
private void appendParam(StringBuilder sb, String key, String value) {
if (StrUtil.isNotBlank(value)) {
if (sb.length() > 0) {
sb.append("&");
}
sb.append(key).append("=").append(value);
}
}
/**
* 解析WPGJ回调数据为PayOrderRespDTO
*/
private PayOrderRespDTO parseWpgjNotifyData(WpgjPayNotifyDTO notifyDTO) {
PayOrderRespDTO notify = new PayOrderRespDTO();
// 根据WPGJ回调文档解析字段
notify.setOutTradeNo(notifyDTO.getMerOrderId()); // 商户订单号
notify.setChannelOrderNo(notifyDTO.getTradeNo()); // 第三方交易号
notify.setStatus(parseWpgjStatus(notifyDTO.getOrderStatus())); // 交易状态
// 设置支付成功时间
if (WpgjOrderStatusEnum.isSuccess(notifyDTO.getOrderStatus())) {
notify.setSuccessTime(java.time.LocalDateTime.now());
}
// 设置其他必要的字段
notify.setChannelCode("wpgj_dynamic");
notify.setChannelErrorCode(null); // 成功时没有错误码
notify.setChannelErrorMsg(null);
notify.setRawData(JsonUtils.toJsonString(notifyDTO)); // 保存原始数据
return notify;
}
/**
* 解析WPGJ交易状态
*/
private Integer parseWpgjStatus(String orderStatus) {
if (WpgjOrderStatusEnum.isSuccess(orderStatus)) { // 1-成功
return com.luhu.computility.module.pay.enums.order.PayOrderStatusEnum.SUCCESS.getStatus();
} else if (WpgjOrderStatusEnum.isFailedOrClosed(orderStatus)) { // 0-处理中 2-失败
return com.luhu.computility.module.pay.enums.order.PayOrderStatusEnum.CLOSED.getStatus();
} else {
return com.luhu.computility.module.pay.enums.order.PayOrderStatusEnum.WAITING.getStatus();
}
}
@PostMapping("/create") @PostMapping("/create")
@Operation(summary = "创建算力资源订单") @Operation(summary = "创建算力资源订单")
......
package com.luhu.computility.module.compute.controller.admin.resourceorder.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* WPGJ旺铺聚合支付回调DTO
*
* @author jonyl
*/
@Schema(description = "WPGJ旺铺聚合支付回调DTO")
@Data
public class WpgjPayNotifyDTO {
@Schema(description = "旺铺平台唯一设备SN号", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "设备编号不能为空")
private String deviceNo;
@Schema(description = "内部商户号", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "商户号不能为空")
private String merNo;
@Schema(description = "商户代码", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "商户代码不能为空")
private String merCode;
@Schema(description = "支付通道代码", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "支付通道代码不能为空")
private String paywayCode;
@Schema(description = "旺铺订单号", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "旺铺订单号不能为空")
private String orderId;
@Schema(description = "商户订单id,系统唯一", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "商户订单号不能为空")
private String merOrderId;
@Schema(description = "网关商户订单号")
private String gatewayMerOrderId;
@Schema(description = "下单时间", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "下单时间不能为空")
private String orderTime;
@Schema(description = "订单金额", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "订单金额不能为空")
private String orderAmt;
@Schema(description = "订单状态", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "订单状态不能为空")
private String orderStatus;
@Schema(description = "通道交易流水号")
private String tradeNo;
@Schema(description = "交易时间,支付成功返回")
private String tradeTime;
@Schema(description = "订单标题")
private String orderTitle;
@Schema(description = "订单手续费")
private String fee;
@Schema(description = "结算金额")
private String actAmt;
@Schema(description = "买家用户号")
private String buyerId;
@Schema(description = "对应微信、支付宝小票上交易单号")
private String tradeTopNo;
@Schema(description = "交易账户类型 C-贷记卡,D-借记卡,U-未知")
private String cardType;
@Schema(description = "报文签名值", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "签名不能为空")
private String sign;
}
\ No newline at end of file
package com.luhu.computility.module.compute.controller.admin.resourceorder.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* WPGJ旺铺聚合支付回调响应DTO
*
* @author jonyl
*/
@Schema(description = "WPGJ旺铺聚合支付回调响应DTO")
@Data
public class WpgjPayNotifyRespDTO {
@Schema(description = "应答码", requiredMode = Schema.RequiredMode.REQUIRED, example = "00")
private String code;
@Schema(description = "对应code码内容描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "成功")
private String msg;
@Schema(description = "对应时间戳", requiredMode = Schema.RequiredMode.REQUIRED, example = "20251027123456789")
private String timestamp;
}
\ No newline at end of file
...@@ -7,7 +7,7 @@ import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppRe ...@@ -7,7 +7,7 @@ import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppRe
import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppResourceOrderPageReqVO; import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppResourceOrderPageReqVO;
import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppResourceOrderRespVO; import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppResourceOrderRespVO;
import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppResourceOrderInvoiceReqVO; import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppResourceOrderInvoiceReqVO;
import com.luhu.computility.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO; import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppWpgjPayOrderSubmitRespVO;
import com.luhu.computility.module.compute.service.resourceorder.ResourceOrderService; import com.luhu.computility.module.compute.service.resourceorder.ResourceOrderService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
...@@ -42,9 +42,9 @@ public class AppResourceOrderController { ...@@ -42,9 +42,9 @@ public class AppResourceOrderController {
@PostMapping("/create-wpgj") @PostMapping("/create-wpgj")
@Operation(summary = "创建算力资源订单(旺铺聚合支付)") @Operation(summary = "创建算力资源订单(旺铺聚合支付)")
public CommonResult<AppPayOrderSubmitRespVO> createResourceOrderWithWpgj(@Valid @RequestBody AppResourceOrderCreateReqVO createReqVO) { public CommonResult<AppWpgjPayOrderSubmitRespVO> createResourceOrderWithWpgj(@Valid @RequestBody AppResourceOrderCreateReqVO createReqVO) {
Long userId = getLoginUserId(); Long userId = getLoginUserId();
AppPayOrderSubmitRespVO respVO = resourceOrderService.createUserResourceOrderWithWpgj(userId, createReqVO); AppWpgjPayOrderSubmitRespVO respVO = resourceOrderService.createUserResourceOrderWithWpgj(userId, createReqVO);
return success(respVO); return success(respVO);
} }
......
package com.luhu.computility.module.compute.controller.app.resourceorder.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* 用户 APP - WPGJ旺铺聚合支付订单提交 Response VO
* 独立于老支付系统的WPGJ支付响应
*
* @author jony
*/
@Schema(description = "用户 APP - WPGJ旺铺聚合支付订单提交 Response VO")
@Data
public class AppWpgjPayOrderSubmitRespVO {
@Schema(description = "订单ID(用于前端轮询支付状态)", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
@NotNull(message = "订单ID不能为空")
private Long id;
@Schema(description = "支付状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@NotNull(message = "支付状态不能为空")
private Integer status;
@Schema(description = "展示模式", example = "url")
private String displayMode;
@Schema(description = "展示内容(支付链接等)", example = "https://example.com/pay")
private String displayContent;
}
\ No newline at end of file
...@@ -72,4 +72,10 @@ public interface ResourceOrderMapper extends BaseMapperX<ResourceOrderDO> { ...@@ -72,4 +72,10 @@ public interface ResourceOrderMapper extends BaseMapperX<ResourceOrderDO> {
.orderByDesc(ResourceOrderDO::getId)); .orderByDesc(ResourceOrderDO::getId));
} }
default ResourceOrderDO selectByOrderNo(String orderNo) {
return selectOne(new LambdaQueryWrapperX<ResourceOrderDO>()
.eq(ResourceOrderDO::getOrderNo, orderNo)
.eq(ResourceOrderDO::getDeleted, false));
}
} }
\ No newline at end of file
...@@ -8,7 +8,8 @@ import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppRe ...@@ -8,7 +8,8 @@ import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppRe
import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppResourceOrderPageReqVO; import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppResourceOrderPageReqVO;
import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppResourceOrderRespVO; import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppResourceOrderRespVO;
import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppResourceOrderInvoiceReqVO; import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppResourceOrderInvoiceReqVO;
import com.luhu.computility.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO; import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppWpgjPayOrderSubmitRespVO;
import com.luhu.computility.module.pay.controller.admin.notify.vo.WpgjPayNotifyDTO;
import com.luhu.computility.module.compute.dal.dataobject.resourceorder.ResourceOrderDO; import com.luhu.computility.module.compute.dal.dataobject.resourceorder.ResourceOrderDO;
import com.luhu.computility.framework.common.pojo.PageResult; import com.luhu.computility.framework.common.pojo.PageResult;
import com.luhu.computility.framework.common.pojo.PageParam; import com.luhu.computility.framework.common.pojo.PageParam;
...@@ -79,9 +80,9 @@ public interface ResourceOrderService { ...@@ -79,9 +80,9 @@ public interface ResourceOrderService {
* *
* @param userId 用户ID * @param userId 用户ID
* @param createReqVO 创建信息 * @param createReqVO 创建信息
* @return 支付提交响应 * @return WPGJ支付提交响应
*/ */
AppPayOrderSubmitRespVO createUserResourceOrderWithWpgj(Long userId, @Valid AppResourceOrderCreateReqVO createReqVO); AppWpgjPayOrderSubmitRespVO createUserResourceOrderWithWpgj(Long userId, @Valid AppResourceOrderCreateReqVO createReqVO);
/** /**
* 更新订单为已支付(支付回调使用) * 更新订单为已支付(支付回调使用)
...@@ -132,4 +133,28 @@ public interface ResourceOrderService { ...@@ -132,4 +133,28 @@ public interface ResourceOrderService {
*/ */
boolean updateRequestInvoice(AppResourceOrderInvoiceReqVO reqVO); boolean updateRequestInvoice(AppResourceOrderInvoiceReqVO reqVO);
/**
* 根据订单号获取算力资源订单
*
* @param orderNo 订单号
* @return 算力资源订单
*/
ResourceOrderDO getResourceOrderByOrderNo(String orderNo);
/**
* WPGJ支付成功回调更新订单状态
*
* @param orderId 订单ID
* @param notifyDTO WPGJ回调通知
*/
void updateOrderPaidByWpgj(Long orderId, WpgjPayNotifyDTO notifyDTO);
/**
* WPGJ支付失败回调更新订单状态
*
* @param orderId 订单ID
* @param notifyDTO WPGJ回调通知
*/
void updateOrderFailedByWpgj(Long orderId, WpgjPayNotifyDTO notifyDTO);
} }
\ No newline at end of file
...@@ -2,6 +2,8 @@ package com.luhu.computility.module.pay.api.order; ...@@ -2,6 +2,8 @@ package com.luhu.computility.module.pay.api.order;
import com.luhu.computility.module.pay.api.order.dto.PayOrderCreateReqDTO; import com.luhu.computility.module.pay.api.order.dto.PayOrderCreateReqDTO;
import com.luhu.computility.module.pay.api.order.dto.PayOrderRespDTO; import com.luhu.computility.module.pay.api.order.dto.PayOrderRespDTO;
import com.luhu.computility.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
import com.luhu.computility.module.pay.api.wpgj.vo.WpgjPayOrderSubmitReqVO;
import javax.validation.Valid; import javax.validation.Valid;
...@@ -45,4 +47,12 @@ public interface PayOrderApi { ...@@ -45,4 +47,12 @@ public interface PayOrderApi {
*/ */
void updatePayOrderPrice(Long id, Integer payPrice); void updatePayOrderPrice(Long id, Integer payPrice);
/**
* 提交WPGJ旺铺聚合支付订单
*
* @param reqVO 提交请求
* @return 支付提交响应
*/
AppPayOrderSubmitRespVO submitWpgjOrder(@Valid WpgjPayOrderSubmitReqVO reqVO);
} }
package com.luhu.computility.module.pay.api.order; package com.luhu.computility.module.pay.api.order;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import com.luhu.computility.framework.common.exception.util.ServiceExceptionUtil;
import com.luhu.computility.module.pay.api.order.dto.PayOrderCreateReqDTO; import com.luhu.computility.module.pay.api.order.dto.PayOrderCreateReqDTO;
import com.luhu.computility.module.pay.api.order.dto.PayOrderRespDTO; import com.luhu.computility.module.pay.api.order.dto.PayOrderRespDTO;
import com.luhu.computility.module.pay.api.order.dto.WpgjPayOrderRespDTO;
import com.luhu.computility.module.pay.convert.order.PayOrderConvert; import com.luhu.computility.module.pay.convert.order.PayOrderConvert;
import com.luhu.computility.module.pay.dal.dataobject.order.PayOrderDO; import com.luhu.computility.module.pay.dal.dataobject.order.PayOrderDO;
import com.luhu.computility.module.pay.dal.dataobject.wpgj.PayOrderWpgjDO;
import com.luhu.computility.module.pay.dal.mysql.wpgj.PayOrderWpgjMapper;
import com.luhu.computility.module.pay.enums.ErrorCodeConstants;
import com.luhu.computility.module.pay.enums.WpgjOrderStatusEnum;
import com.luhu.computility.module.pay.enums.order.PayOrderStatusEnum;
import com.luhu.computility.module.pay.framework.pay.core.client.impl.wpgj.WpgjCryptoUtils;
import com.luhu.computility.module.pay.framework.pay.core.client.impl.wpgj.WpgjPayProperties;
import com.luhu.computility.module.pay.service.order.PayOrderService; import com.luhu.computility.module.pay.service.order.PayOrderService;
import com.luhu.computility.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
import com.luhu.computility.module.pay.api.wpgj.vo.WpgjPayOrderSubmitReqVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.validation.Valid;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/** /**
* 支付单 API 实现类 * 支付单 API 实现类
...@@ -15,11 +34,18 @@ import javax.annotation.Resource; ...@@ -15,11 +34,18 @@ import javax.annotation.Resource;
* @author 芋道源码 * @author 芋道源码
*/ */
@Service @Service
@Slf4j
public class PayOrderApiImpl implements PayOrderApi { public class PayOrderApiImpl implements PayOrderApi {
@Resource @Resource
private PayOrderService payOrderService; private PayOrderService payOrderService;
@Resource
private PayOrderWpgjMapper payOrderWpgjMapper;
@Resource
private WpgjPayProperties wpgjPayProperties;
@Override @Override
public Long createOrder(PayOrderCreateReqDTO reqDTO) { public Long createOrder(PayOrderCreateReqDTO reqDTO) {
return payOrderService.createOrder(reqDTO); return payOrderService.createOrder(reqDTO);
...@@ -41,4 +67,161 @@ public class PayOrderApiImpl implements PayOrderApi { ...@@ -41,4 +67,161 @@ public class PayOrderApiImpl implements PayOrderApi {
payOrderService.updatePayOrderPrice(id, payPrice); payOrderService.updatePayOrderPrice(id, payPrice);
} }
@Override
public AppPayOrderSubmitRespVO submitWpgjOrder(@Valid WpgjPayOrderSubmitReqVO reqVO) {
try {
log.info("[submitWpgjOrder] 开始提交WPGJ支付,订单ID: {}", reqVO.getId());
// 1. 查找WPGJ订单
PayOrderWpgjDO wpgjOrder = validateWpgjOrderCanSubmit(reqVO.getId());
// 2. 调用WPGJ API获取支付链接
WpgjPayOrderRespDTO wpgjResp = callWpgjApi(wpgjOrder);
// 3. 更新WPGJ订单状态
updateWpgjOrderAfterSubmit(wpgjOrder, wpgjResp);
// 4. 构建返回结果
AppPayOrderSubmitRespVO respVO = new AppPayOrderSubmitRespVO();
respVO.setStatus(PayOrderStatusEnum.WAITING.getStatus());
respVO.setWpgjOrderId(wpgjOrder.getId()); // 设置WPGJ订单ID供前端轮询使用
if (wpgjResp != null && wpgjResp.getDisplayContent() != null) {
respVO.setDisplayMode(wpgjResp.getDisplayMode());
respVO.setDisplayContent(wpgjResp.getDisplayContent());
}
return respVO;
} catch (Exception e) {
log.error("[submitWpgjOrder] 提交WPGJ支付失败", e);
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_SUBMIT_CHANNEL_ERROR,
"WPGJ_ERROR", "WPGJ支付提交失败: " + e.getMessage());
}
}
/**
* 校验WPGJ订单是否可以提交
*/
private PayOrderWpgjDO validateWpgjOrderCanSubmit(Long id) {
PayOrderWpgjDO wpgjOrder = payOrderWpgjMapper.selectById(id);
if (wpgjOrder == null) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND);
}
if (WpgjOrderStatusEnum.isSuccess(wpgjOrder.getOrderStatus())) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_SUCCESS);
}
if (!WpgjOrderStatusEnum.PROCESSING.getStatus().equals(wpgjOrder.getOrderStatus())) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
}
return wpgjOrder;
}
/**
* 调用WPGJ支付API
*/
private WpgjPayOrderRespDTO callWpgjApi(PayOrderWpgjDO wpgjOrder) {
try {
log.info("[callWpgjApi] 开始调用WPGJ支付API,订单号: {}", wpgjOrder.getMerOrderId());
// 1. 构建业务数据
JSONObject businessData = new JSONObject();
businessData.set("mer_no", wpgjPayProperties.getMerNo());
businessData.set("mer_code", wpgjPayProperties.getMerCode());
businessData.set("term_code", wpgjPayProperties.getTermCode());
businessData.set("mer_order_id", wpgjOrder.getMerOrderId());
businessData.set("order_amt", String.valueOf(wpgjOrder.getOrderAmt()));
businessData.set("organiz_no", wpgjPayProperties.getOrganizNo());
businessData.set("cashier_type", "1");
businessData.set("return_url", ""); // WPGJ订单暂不使用return_url
businessData.set("notifyurl", wpgjPayProperties.getNotifyUrl());
// 2. 构建加密请求数据
String encryptedRequest = WpgjCryptoUtils.buildEncryptedRequest(
businessData,
wpgjPayProperties.getOrganizNo(),
wpgjPayProperties.getPublicKey()
);
// 3. URL编码
String encodedRequest = URLEncoder.encode(encryptedRequest, "UTF-8");
log.info("[callWpgjApi] 发送加密请求数据: {}", encryptedRequest);
// 4. 发送HTTP请求
HttpResponse response = HttpRequest.post(wpgjPayProperties.getApiUrl())
.header("Content-Type", "application/x-www-form-urlencoded")
.body(encodedRequest)
.timeout(30000)
.execute();
// 5. 处理响应
if (!response.isOk()) {
log.error("[callWpgjApi] WPGJ API调用失败,HTTP状态码: {}", response.getStatus());
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_SUBMIT_CHANNEL_ERROR,
"HTTP_ERROR", "支付网关调用失败,状态码: " + response.getStatus());
}
String responseData = response.body();
// 6. 解析响应结果
JSONObject responseJson = new JSONObject(responseData);
if ("0000".equals(responseJson.getStr("code"))) {
JSONObject decryptedResponse = WpgjCryptoUtils.decryptResponse(responseData, wpgjPayProperties.getPrivateKey());
log.info("[callWpgjApi] WPGJ API解密后的完整响应: {}", decryptedResponse);
return parseWpgjApiResponse(decryptedResponse, wpgjOrder.getMerOrderId());
} else {
return parseWpgjError(responseJson, wpgjOrder.getMerOrderId());
}
} catch (Exception e) {
log.error("[callWpgjApi] WPGJ API调用异常,订单号: {}", wpgjOrder.getMerOrderId(), e);
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_SUBMIT_CHANNEL_ERROR,
"API_ERROR", "支付网关调用异常: " + e.getMessage());
}
}
/**
* 解析WPGJ API成功响应
*/
private WpgjPayOrderRespDTO parseWpgjApiResponse(JSONObject response, String outTradeNo) {
WpgjPayOrderRespDTO respDTO = new WpgjPayOrderRespDTO();
respDTO.setMerchantOrderId(outTradeNo);
respDTO.setStatus(PayOrderStatusEnum.WAITING.getStatus());
// 解析data字段中的payUrl
JSONObject data = response.getJSONObject("data");
if (data != null && data.containsKey("payUrl")) {
String payUrl = data.getStr("payUrl");
respDTO.setDisplayMode("pay_url");
respDTO.setDisplayContent(payUrl);
log.info("[parseWpgjApiResponse] 获取到支付URL: {}", payUrl);
}
return respDTO;
}
/**
* 解析WPGJ API错误响应
*/
private WpgjPayOrderRespDTO parseWpgjError(JSONObject response, String outTradeNo) {
WpgjPayOrderRespDTO respDTO = new WpgjPayOrderRespDTO();
respDTO.setMerchantOrderId(outTradeNo);
respDTO.setStatus(PayOrderStatusEnum.CLOSED.getStatus());
respDTO.setChannelErrorCode(response.getStr("code"));
respDTO.setChannelErrorMsg(response.getStr("msg"));
log.error("[parseWpgjError] WPGJ API返回错误: code={}, msg={}",
response.getStr("code"), response.getStr("msg"));
return respDTO;
}
/**
* 更新WPGJ订单提交后的状态
*/
private void updateWpgjOrderAfterSubmit(PayOrderWpgjDO wpgjOrder, WpgjPayOrderRespDTO wpgjResp) {
// 这里可以根据需要更新WPGJ订单的状态,比如记录支付链接等
log.info("[updateWpgjOrderAfterSubmit] WPGJ订单提交完成,订单ID: {}", wpgjOrder.getId());
}
} }
package com.luhu.computility.module.pay.api.order.dto;
import lombok.Data;
/**
* WPGJ旺铺聚合支付响应 DTO
*
* @author jonyl
*/
@Data
public class WpgjPayOrderRespDTO {
/**
* 商户订单号
*/
private String merchantOrderId;
/**
* 支付状态
*/
private Integer status;
/**
* 展示模式
*/
private String displayMode;
/**
* 展示内容(支付URL)
*/
private String displayContent;
/**
* 渠道错误码
*/
private String channelErrorCode;
/**
* 渠道错误信息
*/
private String channelErrorMsg;
}
\ No newline at end of file
package com.luhu.computility.module.pay.api.wpgj;
import com.luhu.computility.module.pay.api.wpgj.dto.PayOrderWpgjCreateReqDTO;
import javax.validation.Valid;
/**
* WPGJ旺铺聚合支付订单 API 接口
*
* @author jony
*/
public interface PayOrderWpgjApi {
/**
* 创建WPGJ旺铺聚合支付订单
*
* @param reqDTO 创建请求
* @return WPGJ订单ID
*/
Long createOrderWpgj(@Valid PayOrderWpgjCreateReqDTO reqDTO);
}
\ No newline at end of file
package com.luhu.computility.module.pay.api.wpgj;
import com.luhu.computility.module.pay.api.wpgj.dto.PayOrderWpgjCreateReqDTO;
import com.luhu.computility.module.pay.dal.dataobject.wpgj.PayOrderWpgjDO;
import com.luhu.computility.module.pay.dal.mysql.wpgj.PayOrderWpgjMapper;
import com.luhu.computility.module.pay.framework.pay.core.client.impl.wpgj.WpgjPayProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* WPGJ旺铺聚合支付订单 API 实现类
*
* @author jony
*/
@Service
@Validated
@Slf4j
public class PayOrderWpgjApiImpl implements PayOrderWpgjApi {
@Resource
private PayOrderWpgjMapper payOrderWpgjMapper;
@Resource
private WpgjPayProperties wpgjPayProperties;
@Override
public Long createOrderWpgj(PayOrderWpgjCreateReqDTO reqDTO) {
try {
log.info("[createOrderWpgj] 开始创建WPGJ支付订单,商户订单ID: {}, 订单金额: {}元",
reqDTO.getMerOrderId(), reqDTO.getOrderAmt());
// 1. 构建PayOrderWpgjDO
PayOrderWpgjDO wpgjOrder = new PayOrderWpgjDO();
wpgjOrder.setBusinessType(reqDTO.getBusinessType());
wpgjOrder.setMerOrderId(reqDTO.getMerOrderId());
wpgjOrder.setOrderNo(reqDTO.getOrderNo());
wpgjOrder.setOrderTitle(reqDTO.getOrderTitle());
wpgjOrder.setOrderAmt(reqDTO.getOrderAmt()); // 单位:元
wpgjOrder.setOrderStatus("0"); // 0-处理中
wpgjOrder.setOrderTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
// orderId字段在WPGJ回调时才会设置,创建时保持null
// 从配置获取WPGJ必要字段
if (wpgjPayProperties != null) {
wpgjOrder.setMerNo(wpgjPayProperties.getMerNo());
wpgjOrder.setMerCode(wpgjPayProperties.getMerCode());
}
// deviceNo、paywayCode、sign等字段在WPGJ回调时才会设置,创建时保持null
// 2. 插入pay_order_wpgj表
payOrderWpgjMapper.insert(wpgjOrder);
Long wpgjOrderId = wpgjOrder.getId();
log.info("[createOrderWpgj] WPGJ订单创建成功,商户订单ID: {}, WPGJ订单ID: {}",
reqDTO.getMerOrderId(), wpgjOrderId);
// 3. 返回WPGJ订单ID
return wpgjOrderId;
} catch (Exception e) {
log.error("[createOrderWpgj] 创建WPGJ支付订单失败", e);
throw new RuntimeException("创建支付订单失败: " + e.getMessage());
}
}
}
\ No newline at end of file
package com.luhu.computility.module.pay.api.wpgj.dto;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
* WPGJ旺铺聚合支付单创建 Request DTO
*/
@Data
public class PayOrderWpgjCreateReqDTO implements Serializable {
/**
* 业务类型 1-算力资源订单 2-API订单
*/
@NotNull(message = "业务类型不能为空")
private Integer businessType;
/**
* 商户订单ID(对应业务订单ID)
*/
@NotEmpty(message = "商户订单ID不能为空")
private String merOrderId;
/**
* 关联的业务订单号
*/
private String orderNo;
/**
* 订单标题
*/
@NotEmpty(message = "订单标题不能为空")
@Length(max = 64, message = "订单标题长度不能超过64")
private String orderTitle;
/**
* 订单金额,单位:元(注意:WPGJ要求单位是元)
*/
@NotEmpty(message = "订单金额不能为空")
private String orderAmt;
}
\ No newline at end of file
package com.luhu.computility.module.pay.api.wpgj.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* WPGJ旺铺聚合支付订单提交 Request VO
* 独立于老支付系统的WPGJ专用VO
*
* @author jony
*/
@Schema(description = "WPGJ旺铺聚合支付订单提交 Request VO")
@Data
public class WpgjPayOrderSubmitReqVO {
@Schema(description = "WPGJ支付订单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
@NotNull(message = "WPGJ支付订单ID不能为空")
private Long id;
}
\ No newline at end of file
package com.luhu.computility.module.pay.controller.admin.notify;
import cn.hutool.core.util.StrUtil;
import com.luhu.computility.framework.common.util.json.JsonUtils;
import com.luhu.computility.module.pay.controller.admin.notify.vo.WpgjPayNotifyDTO;
import com.luhu.computility.module.pay.controller.admin.notify.vo.WpgjPayNotifyRespDTO;
import com.luhu.computility.module.pay.enums.order.WpgjOrderStatusEnum;
import com.luhu.computility.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO;
import com.luhu.computility.module.pay.framework.pay.core.client.impl.wpgj.WpgjPayProperties;
import com.luhu.computility.module.pay.service.order.PayOrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.security.PermitAll;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.RequestBody;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
/**
* WPGJ旺铺聚合支付回调Controller
*
* @author jonyl
*/
@Tag(name = "管理后台 - WPGJ旺铺聚合支付回调")
@RestController
@RequestMapping("/pay/wpgj")
@Slf4j
public class WpgjPayController {
@Resource
private PayOrderService payOrderService;
@Resource
private WpgjPayProperties wpgjPayProperties;
@PostMapping("/notify")
@PermitAll
@Operation(summary = "WPGJ支付异步回调通知")
public WpgjPayNotifyRespDTO notifyWpgjPay(@RequestBody WpgjPayNotifyDTO notifyDTO) {
WpgjPayNotifyRespDTO response = new WpgjPayNotifyRespDTO();
try {
log.info("[notifyWpgjPay] 收到WPGJ支付回调: {}", JsonUtils.toJsonString(notifyDTO));
// 1. 验证签名
if (!verifyWpgjSignature(notifyDTO)) {
log.error("[notifyWpgjPay] WPGJ回调签名验证失败");
response.setCode("99");
response.setMsg("签名验证失败");
response.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
return response;
}
// 2. 解析回调数据,构造PayOrderRespDTO
PayOrderRespDTO notify = parseWpgjNotifyData(notifyDTO);
// 3. 处理支付结果通知
// 由于WPGJ是特殊渠道,直接调用内部处理方法
payOrderService.notifyOrder(-1L, notify); // 使用-1表示WPGJ特殊渠道
log.info("[notifyWpgjPay] WPGJ支付回调处理完成");
response.setCode("00");
response.setMsg("成功");
response.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
return response;
} catch (Exception e) {
log.error("[notifyWpgjPay] WPGJ支付回调处理失败", e);
response.setCode("99");
response.setMsg("处理失败");
response.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
return response;
}
}
/**
* 验证WPGJ回调签名
*/
private boolean verifyWpgjSignature(WpgjPayNotifyDTO notifyDTO) {
try {
// 1. 构建待签名字符串
StringBuilder sb = new StringBuilder();
// 按照字段名ASCII码排序
Map<String, String> sortedParams = new java.util.TreeMap<>();
sortedParams.put("device_no", notifyDTO.getDeviceNo());
sortedParams.put("mer_no", notifyDTO.getMerNo());
sortedParams.put("payway_code", notifyDTO.getPaywayCode());
sortedParams.put("order_id", notifyDTO.getOrderId());
sortedParams.put("mer_order_id", notifyDTO.getMerOrderId());
sortedParams.put("order_time", notifyDTO.getOrderTime());
sortedParams.put("order_amt", notifyDTO.getOrderAmt());
sortedParams.put("order_status", notifyDTO.getOrderStatus());
// 可选字段(非空才加入)
if (StrUtil.isNotBlank(notifyDTO.getMerCode())) {
sortedParams.put("mer_code", notifyDTO.getMerCode());
}
if (StrUtil.isNotBlank(notifyDTO.getGatewayMerOrderId())) {
sortedParams.put("gateway_mer_order_id", notifyDTO.getGatewayMerOrderId());
}
if (StrUtil.isNotBlank(notifyDTO.getTradeNo())) {
sortedParams.put("trade_no", notifyDTO.getTradeNo());
}
if (StrUtil.isNotBlank(notifyDTO.getTradeTime())) {
sortedParams.put("trade_time", notifyDTO.getTradeTime());
}
if (StrUtil.isNotBlank(notifyDTO.getOrderTitle())) {
sortedParams.put("order_title", notifyDTO.getOrderTitle());
}
if (StrUtil.isNotBlank(notifyDTO.getFee())) {
sortedParams.put("fee", notifyDTO.getFee());
}
if (StrUtil.isNotBlank(notifyDTO.getActAmt())) {
sortedParams.put("act_amt", notifyDTO.getActAmt());
}
if (StrUtil.isNotBlank(notifyDTO.getBuyerId())) {
sortedParams.put("buyer_id", notifyDTO.getBuyerId());
}
if (StrUtil.isNotBlank(notifyDTO.getTradeTopNo())) {
sortedParams.put("trade_top_no", notifyDTO.getTradeTopNo());
}
if (StrUtil.isNotBlank(notifyDTO.getCardType())) {
sortedParams.put("card_type", notifyDTO.getCardType());
}
// 2. 构建签名字符串
for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
if (StrUtil.isNotBlank(entry.getValue())) {
if (sb.length() > 0) {
sb.append("&");
}
sb.append(entry.getKey()).append("=").append(entry.getValue());
}
}
// 3. 添加key
String signKey = wpgjPayProperties.getSignKey(); // 签名密钥
sb.append("&key=").append(signKey);
// 4. 计算MD5签名
String signStr = sb.toString();
String calculatedSign = cn.hutool.crypto.digest.DigestUtil.md5Hex(signStr).toUpperCase();
log.info("[verifyWpgjSignature] 待签名字符串: {}", signStr);
log.info("[verifyWpgjSignature] 计算签名: {}", calculatedSign);
log.info("[verifyWpgjSignature] 原始签名: {}", notifyDTO.getSign());
// 5. 验证签名
return calculatedSign.equals(notifyDTO.getSign());
} catch (Exception e) {
log.error("[verifyWpgjSignature] 签名验证异常", e);
return false;
}
}
/**
* 解析WPGJ回调数据为PayOrderRespDTO
*/
private PayOrderRespDTO parseWpgjNotifyData(WpgjPayNotifyDTO notifyDTO) {
PayOrderRespDTO notify = new PayOrderRespDTO();
// 根据WPGJ回调文档解析字段
notify.setOutTradeNo(notifyDTO.getMerOrderId()); // 商户订单号
notify.setChannelOrderNo(notifyDTO.getTradeNo()); // 第三方交易号
notify.setStatus(parseWpgjStatus(notifyDTO.getOrderStatus())); // 交易状态
// 设置支付成功时间
if (WpgjOrderStatusEnum.isSuccess(notifyDTO.getOrderStatus())) {
notify.setSuccessTime(java.time.LocalDateTime.now());
}
// 设置其他必要的字段
notify.setChannelCode("wpgj_dynamic");
notify.setChannelErrorCode(null); // 成功时没有错误码
notify.setChannelErrorMsg(null);
notify.setRawData(JsonUtils.toJsonString(notifyDTO)); // 保存原始数据
return notify;
}
/**
* 解析WPGJ交易状态
*/
private Integer parseWpgjStatus(String orderStatus) {
if (WpgjOrderStatusEnum.isSuccess(orderStatus)) { // 1-成功
return com.luhu.computility.module.pay.enums.order.PayOrderStatusEnum.SUCCESS.getStatus();
} else if (WpgjOrderStatusEnum.isFailedOrClosed(orderStatus)) { // 0-处理中 2-失败
return com.luhu.computility.module.pay.enums.order.PayOrderStatusEnum.CLOSED.getStatus();
} else {
return com.luhu.computility.module.pay.enums.order.PayOrderStatusEnum.WAITING.getStatus();
}
}
}
\ No newline at end of file
package com.luhu.computility.module.pay.controller.app.order; package com.luhu.computility.module.pay.controller.app.order;
import cn.hutool.core.util.ObjectUtil;
import com.luhu.computility.framework.common.pojo.CommonResult; import com.luhu.computility.framework.common.pojo.CommonResult;
import com.luhu.computility.framework.common.util.object.BeanUtils; import com.luhu.computility.framework.common.util.object.BeanUtils;
import com.luhu.computility.module.pay.controller.admin.order.vo.PayOrderRespVO; import com.luhu.computility.module.pay.controller.admin.order.vo.PayOrderRespVO;
import com.luhu.computility.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO; import com.luhu.computility.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
import com.luhu.computility.module.pay.controller.app.order.vo.AppPayOrderSubmitReqVO; import com.luhu.computility.module.pay.controller.app.order.vo.AppPayOrderSubmitReqVO;
import com.luhu.computility.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO; import com.luhu.computility.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
import com.luhu.computility.module.pay.controller.app.order.vo.PayOrderWpgjRespVO;
import com.luhu.computility.module.pay.convert.order.PayOrderConvert; import com.luhu.computility.module.pay.convert.order.PayOrderConvert;
import com.luhu.computility.module.pay.dal.dataobject.order.PayOrderDO; import com.luhu.computility.module.pay.dal.dataobject.order.PayOrderDO;
import com.luhu.computility.module.pay.dal.dataobject.wallet.PayWalletDO; import com.luhu.computility.module.pay.dal.dataobject.wallet.PayWalletDO;
import com.luhu.computility.module.pay.dal.dataobject.wpgj.PayOrderWpgjDO;
import com.luhu.computility.module.pay.enums.PayChannelEnum; import com.luhu.computility.module.pay.enums.PayChannelEnum;
import com.luhu.computility.module.pay.enums.order.PayOrderStatusEnum; import com.luhu.computility.module.pay.enums.order.PayOrderStatusEnum;
import com.luhu.computility.module.pay.framework.pay.core.client.impl.wallet.WalletPayClient; import com.luhu.computility.module.pay.framework.pay.core.client.impl.wallet.WalletPayClient;
import com.luhu.computility.module.pay.service.order.PayOrderService; import com.luhu.computility.module.pay.service.order.PayOrderService;
import com.luhu.computility.module.pay.service.wallet.PayWalletService; import com.luhu.computility.module.pay.service.wallet.PayWalletService;
import com.luhu.computility.module.pay.service.wpgj.PayOrderWpgjService;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
...@@ -42,6 +46,8 @@ public class AppPayOrderController { ...@@ -42,6 +46,8 @@ public class AppPayOrderController {
private PayOrderService payOrderService; private PayOrderService payOrderService;
@Resource @Resource
private PayWalletService payWalletService; private PayWalletService payWalletService;
@Resource
private PayOrderWpgjService payOrderWpgjService;
@GetMapping("/get") @GetMapping("/get")
@Operation(summary = "获得支付订单") @Operation(summary = "获得支付订单")
...@@ -78,4 +84,15 @@ public class AppPayOrderController { ...@@ -78,4 +84,15 @@ public class AppPayOrderController {
return success(PayOrderConvert.INSTANCE.convert3(respVO)); return success(PayOrderConvert.INSTANCE.convert3(respVO));
} }
@GetMapping("/wpgj-get")
@Operation(summary = "获得WPGJ旺铺聚合支付订单")
@Parameter(name = "id", description = "WPGJ订单ID", required = true, example = "1024")
public CommonResult<PayOrderWpgjRespVO> getWpgjOrder(@RequestParam("id") Long id) {
PayOrderWpgjDO order = payOrderWpgjService.getById(id);
if (order == null) {
return success(new PayOrderWpgjRespVO());
}
return success(BeanUtils.toBean(order, PayOrderWpgjRespVO.class));
}
} }
...@@ -15,4 +15,7 @@ public class AppPayOrderSubmitRespVO extends PayOrderSubmitRespVO { ...@@ -15,4 +15,7 @@ public class AppPayOrderSubmitRespVO extends PayOrderSubmitRespVO {
@Schema(description = "订单扩展ID(用于前端轮询支付状态)", example = "123456") @Schema(description = "订单扩展ID(用于前端轮询支付状态)", example = "123456")
private Long orderExtensionId; private Long orderExtensionId;
@Schema(description = "WPGJ订单ID(用于前端轮询WPGJ支付状态)", example = "123456")
private Long wpgjOrderId;
} }
package com.luhu.computility.module.pay.controller.app.order.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户 APP - WPGJ旺铺聚合支付订单 Response VO
*
* @author jonyl
*/
@Schema(description = "用户 APP - WPGJ旺铺聚合支付订单 Response VO")
@Data
public class PayOrderWpgjRespVO {
@Schema(description = "订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1895381951077651000")
private Long id;
@Schema(description = "业务类型 1-算力资源订单 2-API订单", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer businessType;
@Schema(description = "商户订单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
private String merOrderId;
@Schema(description = "旺铺订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "WPGJ123456789")
private String orderId;
@Schema(description = "网关商户订单号", example = "GW123456")
private String gatewayMerOrderId;
@Schema(description = "关联的业务订单号", example = "123456")
private String orderNo;
@Schema(description = "内部商户号", requiredMode = Schema.RequiredMode.REQUIRED, example = "M123456")
private String merNo;
@Schema(description = "商户代码", requiredMode = Schema.RequiredMode.REQUIRED, example = "C123456")
private String merCode;
@Schema(description = "设备SN号", requiredMode = Schema.RequiredMode.REQUIRED, example = "SN123456789")
private String deviceNo;
@Schema(description = "支付通道代码", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx_native")
private String paywayCode;
@Schema(description = "下单时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "20241028143000")
private String orderTime;
@Schema(description = "订单金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
private String orderAmt;
@Schema(description = "结算金额", example = "99")
private String actAmt;
@Schema(description = "手续费", example = "1")
private String fee;
@Schema(description = "订单状态 0-处理中 1-支付成功 2-支付失败", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private String orderStatus;
@Schema(description = "通道交易流水号", example = "TXN123456789")
private String tradeNo;
@Schema(description = "交易时间", example = "20241028143030")
private String tradeTime;
@Schema(description = "订单标题", example = "算力资源 - 7天")
private String orderTitle;
@Schema(description = "买家用户号", example = "U123456")
private String buyerId;
@Schema(description = "微信/支付宝小票交易单号", example = "TXN123456789")
private String tradeTopNo;
@Schema(description = "交易账户类型 C-贷记卡 D-借记卡 U-未知", example = "D")
private String cardType;
@Schema(description = "报文签名值", requiredMode = Schema.RequiredMode.REQUIRED, example = "ABC123")
private String sign;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime updateTime;
}
\ No newline at end of file
package com.luhu.computility.module.pay.dal.dataobject.wpgj;
import com.luhu.computility.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* 旺铺聚合支付订单 DO
*
* @author jonyl
*/
@TableName("pay_order_wpgj")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayOrderWpgjDO extends BaseDO {
/**
* 订单编号,自增
*/
@TableId
private Long id;
/**
* 业务类型 1-算力资源订单 2-API订单
*/
private Integer businessType;
/**
* 商户订单ID
*/
private String merOrderId;
/**
* 旺铺订单号
*/
private String orderId;
/**
* 网关商户订单号
*/
private String gatewayMerOrderId;
/**
* 关联的业务订单号
*/
private String orderNo;
/**
* 内部商户号
*/
private String merNo;
/**
* 商户代码
*/
private String merCode;
/**
* 设备SN号
*/
private String deviceNo;
/**
* 支付通道代码
*/
private String paywayCode;
/**
* 下单时间
*/
private String orderTime;
/**
* 订单金额
*/
private String orderAmt;
/**
* 结算金额
*/
private String actAmt;
/**
* 手续费
*/
private String fee;
/**
* 订单状态 0-处理中 1-支付成功 2-支付失败
*/
private String orderStatus;
/**
* 通道交易流水号
*/
private String tradeNo;
/**
* 交易时间
*/
private String tradeTime;
/**
* 订单标题
*/
private String orderTitle;
/**
* 买家用户号
*/
private String buyerId;
/**
* 微信/支付宝小票交易单号
*/
private String tradeTopNo;
/**
* 交易账户类型 C-贷记卡 D-借记卡 U-未知
*/
private String cardType;
/**
* 报文签名值
*/
private String sign;
}
\ No newline at end of file
package com.luhu.computility.module.pay.dal.mysql.wpgj;
import com.luhu.computility.framework.mybatis.core.mapper.BaseMapperX;
import com.luhu.computility.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.luhu.computility.module.pay.dal.dataobject.wpgj.PayOrderWpgjDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 旺铺聚合支付订单 Mapper
*
* @author jonyl
*/
@Mapper
public interface PayOrderWpgjMapper extends BaseMapperX<PayOrderWpgjDO> {
default PayOrderWpgjDO selectByMerOrderId(String merOrderId) {
return selectOne(PayOrderWpgjDO::getMerOrderId, merOrderId);
}
default PayOrderWpgjDO selectByOrderId(String orderId) {
return selectOne(PayOrderWpgjDO::getOrderId, orderId);
}
default PayOrderWpgjDO selectByOrderNo(String orderNo) {
return selectOne(PayOrderWpgjDO::getOrderNo, orderNo);
}
}
\ No newline at end of file
package com.luhu.computility.module.compute.enums; package com.luhu.computility.module.pay.enums;
import com.luhu.computility.framework.common.core.ArrayValuable; import com.luhu.computility.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
......
package com.luhu.computility.module.pay.enums.order;
import com.luhu.computility.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* WPGJ旺铺聚合支付订单状态枚举
*
* @author jonyl
*/
@Getter
@AllArgsConstructor
public enum WpgjOrderStatusEnum implements ArrayValuable<String> {
PROCESSING("0", "处理中"),
SUCCESS("1", "支付成功"),
FAILED("2", "支付失败"),
;
private final String status;
private final String name;
@Override
public String[] array() {
return new String[0];
}
/**
* 判断是否支付成功
*
* @param status 状态
* @return 是否支付成功
*/
public static boolean isSuccess(String status) {
return SUCCESS.getStatus().equals(status);
}
/**
* 判断是否支付失败或关闭
*
* @param status 状态
* @return 是否支付失败或关闭
*/
public static boolean isFailedOrClosed(String status) {
return PROCESSING.getStatus().equals(status) || FAILED.getStatus().equals(status);
}
}
\ No newline at end of file
package com.luhu.computility.module.pay.framework.pay.core.client.dto.order;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* WPGJ 业务数据 DTO
*
* @author jony
*/
@Data
@Accessors(chain = true)
public class WpgjBusinessDataDTO {
/**
* 商户唯一订单号
*/
@NotBlank(message = "商户订单号不能为空")
private String merOrderId;
/**
* 订单金额(分)
*/
@NotBlank(message = "订单金额不能为空")
private String orderAmt;
/**
* 订单标题
*/
private String orderTitle;
/**
* 下单时间
*/
@NotBlank(message = "下单时间不能为空")
private String orderTime;
}
\ No newline at end of file
package com.luhu.computility.module.pay.framework.pay.core.client.dto.order;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* WPGJ 统一下单请求 DTO
*
* @author jony
*/
@Data
@Accessors(chain = true)
public class WpgjUnifiedOrderReqDTO {
/**
* 商户号
*/
@NotBlank(message = "商户号不能为空")
private String merNo;
/**
* 商户代码
*/
@NotBlank(message = "商户代码不能为空")
private String merCode;
/**
* 异步通知地址
*/
@NotBlank(message = "异步通知地址不能为空")
private String notifyUrl;
/**
* 业务数据
*/
@NotBlank(message = "业务数据不能为空")
private String businessData;
}
\ No newline at end of file
...@@ -101,16 +101,6 @@ public interface PayOrderService { ...@@ -101,16 +101,6 @@ public interface PayOrderService {
PayOrderSubmitRespVO submitOrder(@Valid PayOrderSubmitReqVO reqVO, PayOrderSubmitRespVO submitOrder(@Valid PayOrderSubmitReqVO reqVO,
@NotEmpty(message = "提交 IP 不能为空") String userIp); @NotEmpty(message = "提交 IP 不能为空") String userIp);
/**
* 提交旺铺聚合支付订单
* 此时,会发起旺铺聚合支付渠道的调用
*
* @param reqVO 提交请求
* @param userIp 提交 IP
* @return 提交结果
*/
AppPayOrderSubmitRespVO submitWpgjOrder(@Valid AppPayOrderSubmitReqVO reqVO,
@NotEmpty(message = "提交 IP 不能为空") String userIp);
/** /**
* 通知支付单成功 * 通知支付单成功
......
package com.luhu.computility.module.pay.service.wpgj;
import com.luhu.computility.module.pay.controller.admin.notify.vo.WpgjPayNotifyDTO;
import com.luhu.computility.module.pay.dal.dataobject.wpgj.PayOrderWpgjDO;
/**
* 旺铺聚合支付订单 Service 接口
*
* @author jonyl
*/
public interface PayOrderWpgjService {
/**
* 保存旺铺聚合支付订单回调数据
*
* @param notifyDTO WPGJ回调数据
* @return 订单ID
*/
Long saveOrder(WpgjPayNotifyDTO notifyDTO);
/**
* 根据商户订单ID获取旺铺聚合支付订单
*
* @param merOrderId 商户订单ID (对应compute_resource_order.id)
* @return 旺铺聚合支付订单
*/
PayOrderWpgjDO getByMerOrderId(String merOrderId);
/**
* 根据旺铺订单ID获取旺铺聚合支付订单
*
* @param orderId 旺铺订单ID
* @return 旺铺聚合支付订单
*/
PayOrderWpgjDO getByOrderId(String orderId);
/**
* 根据主键ID获取旺铺聚合支付订单
*
* @param id 主键ID
* @return 旺铺聚合支付订单
*/
PayOrderWpgjDO getById(Long id);
}
\ No newline at end of file
package com.luhu.computility.module.pay.service.wpgj;
import cn.hutool.core.util.StrUtil;
import com.luhu.computility.framework.common.util.object.BeanUtils;
import com.luhu.computility.module.pay.controller.admin.notify.vo.WpgjPayNotifyDTO;
import com.luhu.computility.module.pay.dal.dataobject.wpgj.PayOrderWpgjDO;
import com.luhu.computility.module.pay.dal.mysql.wpgj.PayOrderWpgjMapper;
import com.luhu.computility.module.pay.framework.pay.core.client.impl.wpgj.WpgjPayProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
/**
* 旺铺聚合支付订单 Service 实现类
*
* @author jonyl
*/
@Service
@Validated
@Slf4j
public class PayOrderWpgjServiceImpl implements PayOrderWpgjService {
@Resource
private PayOrderWpgjMapper payOrderWpgjMapper;
@Resource
private WpgjPayProperties wpgjPayProperties;
@Override
public Long saveOrder(WpgjPayNotifyDTO notifyDTO) {
// 查询是否已存在相同商户订单ID的记录
PayOrderWpgjDO existingOrder = payOrderWpgjMapper.selectByMerOrderId(notifyDTO.getMerOrderId());
if (existingOrder != null) {
log.info("[saveOrder] WPGJ订单已存在,商户订单ID: {}, 更新订单状态", notifyDTO.getMerOrderId());
// 更新现有订单的状态和信息
PayOrderWpgjDO updateOrder = BeanUtils.toBean(notifyDTO, PayOrderWpgjDO.class);
updateOrder.setId(existingOrder.getId());
updateOrder.setOrderStatus(notifyDTO.getOrderStatus());
payOrderWpgjMapper.updateById(updateOrder);
return existingOrder.getId();
}
// 创建新订单记录
PayOrderWpgjDO payOrderWpgj = BeanUtils.toBean(notifyDTO, PayOrderWpgjDO.class);
payOrderWpgj.setBusinessType(1); // 1-算力资源订单
payOrderWpgj.setOrderNo(notifyDTO.getMerOrderId()); // 商户订单ID作为业务订单号
// 从配置中获取可能为空的必填字段
if (StrUtil.isBlank(payOrderWpgj.getMerCode())) {
payOrderWpgj.setMerCode(wpgjPayProperties.getMerCode());
log.info("[saveOrder] 使用配置中的merCode: {}", wpgjPayProperties.getMerCode());
}
payOrderWpgjMapper.insert(payOrderWpgj);
log.info("[saveOrder] WPGJ订单保存成功,订单ID: {}, 商户订单ID: {}",
payOrderWpgj.getId(), notifyDTO.getMerOrderId());
return payOrderWpgj.getId();
}
@Override
public PayOrderWpgjDO getByMerOrderId(String merOrderId) {
return payOrderWpgjMapper.selectByMerOrderId(merOrderId);
}
@Override
public PayOrderWpgjDO getByOrderId(String orderId) {
return payOrderWpgjMapper.selectByOrderId(orderId);
}
@Override
public PayOrderWpgjDO getById(Long id) {
return payOrderWpgjMapper.selectById(id);
}
}
\ 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