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,9 +53,7 @@ public class ResourceOrderController { ...@@ -57,9 +53,7 @@ public class ResourceOrderController {
@Resource @Resource
private PayOrderService payOrderService; private PayOrderService payOrderService;
@Resource
private WpgjPayProperties wpgjPayProperties;
/** /**
* 内部支付任务回调 * 内部支付任务回调
*/ */
...@@ -72,149 +66,8 @@ public class ResourceOrderController { ...@@ -72,149 +66,8 @@ 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 = "创建算力资源订单")
@PreAuthorize("@ss.hasPermission('compute:resource-order:create')") @PreAuthorize("@ss.hasPermission('compute:resource-order:create')")
......
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
package com.luhu.computility.module.compute.service.resourceorder; package com.luhu.computility.module.compute.service.resourceorder;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.luhu.computility.framework.common.exception.ServiceException; import com.luhu.computility.framework.common.exception.ServiceException;
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.framework.common.util.object.BeanUtils;
import com.luhu.computility.framework.common.util.string.StrUtils; import com.luhu.computility.framework.common.util.string.StrUtils;
import lombok.extern.slf4j.Slf4j; import com.luhu.computility.module.compute.config.ResourceOrderProperties;
import org.springframework.stereotype.Service; import com.luhu.computility.module.compute.controller.admin.resourceorder.vo.ResourceOrderPageReqVO;
import javax.annotation.Resource; import com.luhu.computility.module.compute.controller.admin.resourceorder.vo.ResourceOrderRespVO;
import org.springframework.validation.annotation.Validated; import com.luhu.computility.module.compute.controller.admin.resourceorder.vo.ResourceOrderSaveReqVO;
import org.springframework.transaction.annotation.Transactional; import com.luhu.computility.module.compute.controller.app.resourceorder.vo.*;
import java.time.LocalDateTime;
import java.util.*;
import com.luhu.computility.module.compute.controller.admin.resourceorder.vo.*;
import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppResourceOrderCreateReqVO;
import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppResourceOrderCreateRespVO;
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.AppResourceOrderInvoiceReqVO;
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.PageParam;
import com.luhu.computility.framework.common.util.object.BeanUtils;
import com.luhu.computility.module.compute.dal.mysql.resourceorder.ResourceOrderMapper;
import com.luhu.computility.module.compute.dal.dataobject.resourcesku.ResourceSkuDO; import com.luhu.computility.module.compute.dal.dataobject.resourcesku.ResourceSkuDO;
import com.luhu.computility.module.compute.dal.dataobject.resourcespu.ResourceSpuDO; import com.luhu.computility.module.compute.dal.dataobject.resourcespu.ResourceSpuDO;
import com.luhu.computility.module.compute.dal.mysql.resourceorder.ResourceOrderMapper;
import com.luhu.computility.module.compute.dal.redis.no.ResourceOrderNoRedisDAO;
import com.luhu.computility.module.compute.enums.ResourceOrderInvoiceStatus;
import com.luhu.computility.module.compute.enums.ResourceOrderStatus;
import com.luhu.computility.module.compute.service.resourcecategory.ResourceCategoryService;
import com.luhu.computility.module.compute.service.resourcesku.ResourceSkuService; import com.luhu.computility.module.compute.service.resourcesku.ResourceSkuService;
import com.luhu.computility.module.compute.service.resourcespu.ResourceSpuService; import com.luhu.computility.module.compute.service.resourcespu.ResourceSpuService;
import com.luhu.computility.module.compute.service.resourcecategory.ResourceCategoryService;
import com.luhu.computility.module.compute.enums.ResourceOrderStatus;
import com.luhu.computility.module.compute.enums.ResourceOrderRefundStatus;
import com.luhu.computility.module.compute.enums.ResourceOrderInvoiceStatus;
import com.luhu.computility.module.compute.config.ResourceOrderProperties;
import com.luhu.computility.module.pay.api.order.PayOrderApi; import com.luhu.computility.module.pay.api.order.PayOrderApi;
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.wpgj.PayOrderWpgjApi;
import com.luhu.computility.module.pay.api.wpgj.dto.PayOrderWpgjCreateReqDTO;
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 com.luhu.computility.module.pay.enums.order.PayOrderStatusEnum; import com.luhu.computility.module.pay.enums.order.PayOrderStatusEnum;
import static com.luhu.computility.framework.common.util.servlet.ServletUtils.getClientIP; import com.luhu.computility.module.pay.framework.pay.core.client.impl.wpgj.WpgjPayProperties;
import static com.luhu.computility.framework.common.util.date.LocalDateTimeUtils.addTime; import com.luhu.computility.module.pay.controller.admin.notify.vo.WpgjPayNotifyDTO;
import com.luhu.computility.module.pay.service.order.PayOrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import static com.luhu.computility.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.luhu.computility.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.luhu.computility.framework.common.util.collection.CollectionUtils.convertList; import static com.luhu.computility.framework.common.util.collection.CollectionUtils.convertList;
import static com.luhu.computility.framework.common.util.collection.CollectionUtils.diffList; import static com.luhu.computility.framework.common.util.date.LocalDateTimeUtils.addTime;
import static com.luhu.computility.framework.common.util.servlet.ServletUtils.getClientIP;
import static com.luhu.computility.module.compute.enums.ErrorCodeConstants.*; import static com.luhu.computility.module.compute.enums.ErrorCodeConstants.*;
import static com.luhu.computility.module.compute.enums.ResourceOrderRefundStatus.NOT_REFUND;
import static com.luhu.computility.module.compute.enums.ResourceOrderInvoiceStatus.UNINVOICE; import static com.luhu.computility.module.compute.enums.ResourceOrderInvoiceStatus.UNINVOICE;
import com.luhu.computility.module.compute.dal.redis.no.ResourceOrderNoRedisDAO; import static com.luhu.computility.module.compute.enums.ResourceOrderRefundStatus.NOT_REFUND;
import com.luhu.computility.module.pay.service.order.PayOrderService;
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.enums.order.PayOrderStatusEnum;
/** /**
* 算力资源订单 Service 实现类 * 算力资源订单 Service 实现类
...@@ -80,6 +78,9 @@ public class ResourceOrderServiceImpl implements ResourceOrderService { ...@@ -80,6 +78,9 @@ public class ResourceOrderServiceImpl implements ResourceOrderService {
private PayOrderApi payOrderApi; private PayOrderApi payOrderApi;
@Resource @Resource
private PayOrderWpgjApi payOrderWpgjApi;
@Resource
private PayOrderService payOrderService; private PayOrderService payOrderService;
@Resource @Resource
...@@ -88,6 +89,10 @@ public class ResourceOrderServiceImpl implements ResourceOrderService { ...@@ -88,6 +89,10 @@ public class ResourceOrderServiceImpl implements ResourceOrderService {
@Resource @Resource
private ResourceOrderNoRedisDAO resourceOrderNoRedisDAO; private ResourceOrderNoRedisDAO resourceOrderNoRedisDAO;
@Resource
private WpgjPayProperties wpgjPayProperties;
@Override @Override
public Long createResourceOrder(ResourceOrderSaveReqVO createReqVO) { public Long createResourceOrder(ResourceOrderSaveReqVO createReqVO) {
// 插入 // 插入
...@@ -285,6 +290,7 @@ public class ResourceOrderServiceImpl implements ResourceOrderService { ...@@ -285,6 +290,7 @@ public class ResourceOrderServiceImpl implements ResourceOrderService {
return resourceOrderNoRedisDAO.generate(ResourceOrderNoRedisDAO.RESOURCE_ORDER_NO_PREFIX); return resourceOrderNoRedisDAO.generate(ResourceOrderNoRedisDAO.RESOURCE_ORDER_NO_PREFIX);
} }
/** /**
* 校验支付订单的合法性 * 校验支付订单的合法性
* *
...@@ -443,7 +449,7 @@ public class ResourceOrderServiceImpl implements ResourceOrderService { ...@@ -443,7 +449,7 @@ public class ResourceOrderServiceImpl implements ResourceOrderService {
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public AppPayOrderSubmitRespVO createUserResourceOrderWithWpgj(Long userId, AppResourceOrderCreateReqVO createReqVO) { public AppWpgjPayOrderSubmitRespVO createUserResourceOrderWithWpgj(Long userId, AppResourceOrderCreateReqVO createReqVO) {
// 1. 构建订单 // 1. 构建订单
ResourceOrderDO order = buildResourceOrder(userId, createReqVO); ResourceOrderDO order = buildResourceOrder(userId, createReqVO);
...@@ -451,61 +457,158 @@ public class ResourceOrderServiceImpl implements ResourceOrderService { ...@@ -451,61 +457,158 @@ public class ResourceOrderServiceImpl implements ResourceOrderService {
resourceOrderMapper.insert(order); resourceOrderMapper.insert(order);
// 3. 创建旺铺聚合支付订单 // 3. 创建旺铺聚合支付订单
AppPayOrderSubmitRespVO payRespVO = null; AppWpgjPayOrderSubmitRespVO payRespVO = null;
if (order.getPaymentPrice() > 0) { if (order.getPaymentPrice() > 0) {
payRespVO = createPayOrderWithWpgj(order); payRespVO = createWpgjPayOrder(order);
} else { } else {
// 如果价格为0,返回成功状态 // 如果价格为0,返回成功状态,使用资源订单ID
payRespVO = new AppPayOrderSubmitRespVO(); payRespVO = new AppWpgjPayOrderSubmitRespVO();
payRespVO.setId(order.getId()); // 价格为0时没有WPGJ订单ID,使用资源订单ID
payRespVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); payRespVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
} }
// 4. 保存支付订单ID到响应中(用于前端轮询)
if (payRespVO != null && order.getPayOrderId() != null) {
// 通过扩展字段传递payOrderId
payRespVO.setOrderExtensionId(order.getPayOrderId());
}
return payRespVO; return payRespVO;
} }
/** /**
* 创建旺铺聚合支付订单 * 创建WPGJ旺铺聚合支付订单(完全独立实现)
*/ */
private AppPayOrderSubmitRespVO createPayOrderWithWpgj(ResourceOrderDO order) { private AppWpgjPayOrderSubmitRespVO createWpgjPayOrder(ResourceOrderDO order) {
try { try {
// 1. 创建支付订单(用于数据库记录) log.info("[createWpgjPayOrder] 开始创建WPGJ支付订单,资源订单ID: {}, 支付金额: {}",
PayOrderCreateReqDTO payOrderCreateReqDTO = new PayOrderCreateReqDTO() order.getId(), order.getPaymentPrice());
.setAppKey(resourceOrderProperties.getPayAppKey()).setUserIp(order.getUserIp());
payOrderCreateReqDTO.setMerchantOrderId(String.valueOf(order.getId())); // 1. 创建WPGJ支付订单记录
PayOrderWpgjCreateReqDTO wpgjCreateReqDTO = new PayOrderWpgjCreateReqDTO();
wpgjCreateReqDTO.setBusinessType(1); // 1-算力资源订单
wpgjCreateReqDTO.setMerOrderId(String.valueOf(order.getId()));
String subject = order.getSpuName() + " - " + getDurationDaysFromSku(order.getSkuId()) + "天"; String subject = order.getSpuName() + " - " + getDurationDaysFromSku(order.getSkuId()) + "天";
subject = StrUtils.maxLength(subject, PayOrderCreateReqDTO.SUBJECT_MAX_LENGTH); subject = StrUtils.maxLength(subject, 64); // WPGJ订单标题最大64位
payOrderCreateReqDTO.setSubject(subject); wpgjCreateReqDTO.setOrderTitle(subject);
payOrderCreateReqDTO.setBody(subject); wpgjCreateReqDTO.setOrderAmt(String.valueOf(order.getPaymentPrice() / 100.0)); // 转换为元
payOrderCreateReqDTO.setPrice(order.getPaymentPrice().intValue())
.setExpireTime(addTime(resourceOrderProperties.getPayExpireTime())); Long wpgjOrderId = payOrderWpgjApi.createOrderWpgj(wpgjCreateReqDTO);
log.info("[createWpgjPayOrder] WPGJ订单创建成功,资源订单ID: {}, WPGJ订单ID: {}", order.getId(), wpgjOrderId);
Long payOrderId = payOrderApi.createOrderWpgj(payOrderCreateReqDTO); // 2. 更新算力资源订单的WPGJ订单ID
resourceOrderMapper.updateById(new ResourceOrderDO().setId(order.getId()).setPayOrderId(wpgjOrderId));
// 2. 提交支付订单(使用旺铺聚合支付) // 3. 调用WPGJ支付提交API获取支付链接
AppPayOrderSubmitReqVO submitReqDTO = new AppPayOrderSubmitReqVO(); WpgjPayOrderSubmitReqVO submitReqVO = new WpgjPayOrderSubmitReqVO();
submitReqDTO.setId(payOrderId); submitReqVO.setId(wpgjOrderId);
submitReqDTO.setChannelCode("wpgj_dynamic");
AppPayOrderSubmitRespVO submitRespVO = payOrderService.submitWpgjOrder(submitReqDTO, order.getUserIp()); AppPayOrderSubmitRespVO submitRespVO = payOrderApi.submitWpgjOrder(submitReqVO);
log.info("[createWpgjPayOrder] WPGJ支付提交成功,资源订单ID: {}, WPGJ订单ID: {}, 支付状态: {}",
order.getId(), wpgjOrderId, submitRespVO.getStatus());
// 3. 更新到算力资源订单上 // 4. 构建返回数据 - 包含支付链接
resourceOrderMapper.updateById(new ResourceOrderDO().setId(order.getId()).setPayOrderId(payOrderId)); AppWpgjPayOrderSubmitRespVO respVO = new AppWpgjPayOrderSubmitRespVO();
order.setPayOrderId(payOrderId); respVO.setId(wpgjOrderId); // 返回WPGJ订单ID,用于前端轮询
respVO.setStatus(submitRespVO.getStatus());
respVO.setDisplayMode(submitRespVO.getDisplayMode());
respVO.setDisplayContent(submitRespVO.getDisplayContent()); // 支付链接
log.info("[createPayOrderWithWpgj] 旺铺聚合支付订单创建成功,订单ID: {}, 支付订单ID: {}", order.getId(), payOrderId); log.info("[createWpgjPayOrder] WPGJ支付订单创建完成,资源订单ID: {}, WPGJ订单ID: {}, 支付链接: {}",
order.getId(), wpgjOrderId, submitRespVO.getDisplayContent());
return submitRespVO; return respVO;
} catch (Exception e) { } catch (Exception e) {
log.error("[createPayOrderWithWpgj] 创建旺铺聚合支付订单失败", e); log.error("[createWpgjPayOrder] 创建WPGJ支付订单失败", e);
throw new ServiceException("创建支付订单失败: " + e.getMessage()); throw new ServiceException("创建支付订单失败: " + e.getMessage());
} }
} }
@Override
public ResourceOrderDO getResourceOrderByOrderNo(String orderNo) {
return resourceOrderMapper.selectByOrderNo(orderNo);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateOrderPaidByWpgj(Long orderId, WpgjPayNotifyDTO notifyDTO) {
try {
log.info("[updateOrderPaidByWpgj] 开始处理WPGJ支付成功回调,订单ID: {}, WPGJ订单号: {}",
orderId, notifyDTO.getOrderId());
// 1. 校验算力资源订单是否存在
ResourceOrderDO order = validateResourceOrderExists(orderId);
// 2. 校验订单状态,避免重复处理
if (ResourceOrderStatus.PAID.getValue() == order.getStatus()) {
log.warn("[updateOrderPaidByWpgj] 订单已经是支付状态,订单ID: {}", orderId);
return;
}
if (!ResourceOrderStatus.UNPAID.getValue().equals(order.getStatus())) {
log.error("[updateOrderPaidByWpgj] 订单状态不是待支付,无法更新为已支付,订单ID: {}, 当前状态: {}",
orderId, order.getStatus());
return;
}
// 3. 校验支付金额是否匹配
String wpgjOrderAmt = notifyDTO.getOrderAmt(); // WPGJ回调金额单位是元
long resourceOrderAmt = order.getPaymentPrice() / 100; // 转换为元
if (!wpgjOrderAmt.equals(String.valueOf(resourceOrderAmt))) {
log.error("[updateOrderPaidByWpgj] 支付金额不匹配,订单ID: {}, WPGJ金额: {}, 订单金额: {}",
orderId, wpgjOrderAmt, resourceOrderAmt);
return;
}
// 4. 更新算力资源订单状态为已支付
ResourceOrderDO updateOrder = new ResourceOrderDO();
updateOrder.setId(orderId);
updateOrder.setStatus(ResourceOrderStatus.PAID.getValue());
updateOrder.setPayTime(LocalDateTime.now());
resourceOrderMapper.updateById(updateOrder);
log.info("[updateOrderPaidByWpgj] WPGJ支付成功回调处理完成,订单ID: {}, WPGJ订单号: {}",
orderId, notifyDTO.getOrderId());
} catch (Exception e) {
log.error("[updateOrderPaidByWpgj] 处理WPGJ支付成功回调异常,订单ID: {}", orderId, e);
throw e;
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateOrderFailedByWpgj(Long orderId, WpgjPayNotifyDTO notifyDTO) {
try {
log.info("[updateOrderFailedByWpgj] 开始处理WPGJ支付失败回调,订单ID: {}, WPGJ订单号: {}",
orderId, notifyDTO.getOrderId());
// 1. 校验算力资源订单是否存在
ResourceOrderDO order = validateResourceOrderExists(orderId);
// 2. 校验订单状态,避免重复处理
if (ResourceOrderStatus.CANCELED.getValue() == order.getStatus()) {
log.warn("[updateOrderFailedByWpgj] 订单已经是取消状态,订单ID: {}", orderId);
return;
}
if (!ResourceOrderStatus.UNPAID.getValue().equals(order.getStatus())) {
log.error("[updateOrderFailedByWpgj] 订单状态不是待支付,无法更新为已取消,订单ID: {}, 当前状态: {}",
orderId, order.getStatus());
return;
}
// 3. 更新算力资源订单状态为已取消(支付失败)
ResourceOrderDO updateOrder = new ResourceOrderDO();
updateOrder.setId(orderId);
updateOrder.setStatus(ResourceOrderStatus.CANCELED.getValue());
updateOrder.setCancelTime(LocalDateTime.now());
resourceOrderMapper.updateById(updateOrder);
log.info("[updateOrderFailedByWpgj] WPGJ支付失败回调处理完成,订单ID: {}, WPGJ订单号: {}",
orderId, notifyDTO.getOrderId());
} catch (Exception e) {
log.error("[updateOrderFailedByWpgj] 处理WPGJ支付失败回调异常,订单ID: {}", orderId, e);
throw e;
}
}
} }
\ 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,17 +101,7 @@ public interface PayOrderService { ...@@ -101,17 +101,7 @@ 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);
/** /**
* 通知支付单成功 * 通知支付单成功
* *
......
...@@ -14,8 +14,6 @@ import com.luhu.computility.framework.common.util.number.MoneyUtils; ...@@ -14,8 +14,6 @@ import com.luhu.computility.framework.common.util.number.MoneyUtils;
import com.luhu.computility.module.pay.framework.pay.core.client.PayClient; import com.luhu.computility.module.pay.framework.pay.core.client.PayClient;
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.dto.order.PayOrderUnifiedReqDTO; import com.luhu.computility.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
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.framework.tenant.core.util.TenantUtils; import com.luhu.computility.framework.tenant.core.util.TenantUtils;
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.controller.admin.order.vo.PayOrderExportReqVO; import com.luhu.computility.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
...@@ -74,7 +72,7 @@ public class PayOrderServiceImpl implements PayOrderService { ...@@ -74,7 +72,7 @@ public class PayOrderServiceImpl implements PayOrderService {
private PayOrderExtensionMapper orderExtensionMapper; private PayOrderExtensionMapper orderExtensionMapper;
@Resource @Resource
private PayNoRedisDAO noRedisDAO; private PayNoRedisDAO noRedisDAO;
@Resource @Resource
private PayAppService appService; private PayAppService appService;
@Resource @Resource
...@@ -82,9 +80,7 @@ public class PayOrderServiceImpl implements PayOrderService { ...@@ -82,9 +80,7 @@ public class PayOrderServiceImpl implements PayOrderService {
@Resource @Resource
private PayNotifyService notifyService; private PayNotifyService notifyService;
@Resource
private WpgjPayProperties wpgjPayProperties;
@Override @Override
public PayOrderDO getOrder(Long id) { public PayOrderDO getOrder(Long id) {
return payOrderMapper.selectById(id); return payOrderMapper.selectById(id);
...@@ -146,30 +142,11 @@ public class PayOrderServiceImpl implements PayOrderService { ...@@ -146,30 +142,11 @@ public class PayOrderServiceImpl implements PayOrderService {
@Override @Override
public Long createOrderWpgj(PayOrderCreateReqDTO reqDTO) { public Long createOrderWpgj(PayOrderCreateReqDTO reqDTO) {
// 校验 App // WPGJ订单已迁移到API层,这里提供兼容性实现
PayAppDO app = appService.validPayApp(reqDTO.getAppKey()); throw new UnsupportedOperationException("WPGJ订单创建已迁移到PayOrderApi,请使用PayOrderApi.createOrderWpgj");
// 查询对应的支付交易单是否已经存在。如果是,则直接返回
PayOrderDO order = payOrderMapper.selectByAppIdAndMerchantOrderId(
app.getId(), reqDTO.getMerchantOrderId());
if (order != null) {
log.warn("[createOrderWpgj][appId({}) merchantOrderId({}) 已经存在对应的支付单({})]", order.getAppId(),
order.getMerchantOrderId(), toJsonString(order)); // 理论来说,不会出现这个情况
return order.getId();
}
// 创建WPGJ支付交易单,使用WPGJ专用回调URL
order = PayOrderConvert.INSTANCE.convert(reqDTO).setAppId(app.getId())
// 商户相关字段 - 使用WPGJ专用回调URL
.setNotifyUrl(wpgjPayProperties.getNotifyUrl())
// 订单相关字段
.setStatus(PayOrderStatusEnum.WAITING.getStatus())
// 退款相关字段
.setRefundPrice(0);
payOrderMapper.insert(order);
return order.getId();
} }
@Override // 注意,这里不能添加事务注解,避免调用支付渠道失败时,将 PayOrderExtensionDO 回滚了 @Override // 注意,这里不能添加事务注解,避免调用支付渠道失败时,将 PayOrderExtensionDO 回滚了
public PayOrderSubmitRespVO submitOrder(PayOrderSubmitReqVO reqVO, String userIp) { public PayOrderSubmitRespVO submitOrder(PayOrderSubmitReqVO reqVO, String userIp) {
// 1.1 获得 PayOrderDO ,并校验其是否存在 // 1.1 获得 PayOrderDO ,并校验其是否存在
...@@ -218,154 +195,9 @@ public class PayOrderServiceImpl implements PayOrderService { ...@@ -218,154 +195,9 @@ public class PayOrderServiceImpl implements PayOrderService {
return PayOrderConvert.INSTANCE.convert(order, unifiedOrderResp); return PayOrderConvert.INSTANCE.convert(order, unifiedOrderResp);
} }
/**
* 提交旺铺聚合支付订单
*
* @param reqVO 支付提交请求
* @param userIp 用户 IP
* @return 支付提交响应
*/
// 注意,这里不能添加事务注解,避免调用支付渠道失败时,将 PayOrderExtensionDO 回滚了
public AppPayOrderSubmitRespVO submitWpgjOrder(AppPayOrderSubmitReqVO reqVO, String userIp) {
// 1.1 获得 PayOrderDO ,并校验其是否存在
PayOrderDO order = validateOrderCanSubmit(reqVO.getId());
// 2. 插入 PayOrderExtensionDO
String no = noRedisDAO.generate(payProperties.getOrderNoPrefix());
PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqVO, userIp)
.setOrderId(order.getId()).setNo(no)
.setChannelId(-1L).setChannelCode("wpgj_dynamic") // 使用-1表示WPGJ特殊渠道,避免在validateOrderActuallyPaid中查找PayClient
.setStatus(PayOrderStatusEnum.WAITING.getStatus());
orderExtensionMapper.insert(orderExtension);
// 3. 直接调用WPGJ API
PayOrderRespDTO unifiedOrderResp = callWpgjApi(order, orderExtension, reqVO, userIp);
// 4. 构建返回结果
AppPayOrderSubmitRespVO appRespVO = new AppPayOrderSubmitRespVO();
if (unifiedOrderResp != null) {
appRespVO.setStatus(PayOrderStatusEnum.WAITING.getStatus());
appRespVO.setDisplayMode(unifiedOrderResp.getDisplayMode());
appRespVO.setDisplayContent(unifiedOrderResp.getDisplayContent());
} else {
appRespVO.setStatus(order.getStatus());
}
return appRespVO;
}
/**
* 调用WPGJ支付API - 按照demo代码标准实现动态支付序列码接口
*/
private PayOrderRespDTO callWpgjApi(PayOrderDO order, PayOrderExtensionDO orderExtension,
AppPayOrderSubmitReqVO reqVO, String userIp) {
try {
log.info("[callWpgjApi] 开始调用WPGJ支付API,订单号: {}", orderExtension.getNo());
// 1. 构建业务数据 - 按照demo的testDynamicOrder方法
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", orderExtension.getNo()); // 商户唯一订单号
businessData.set("order_amt", String.valueOf(order.getPrice() / 100.0)); // 转换为元,保留两位小数
businessData.set("organiz_no", wpgjPayProperties.getOrganizNo());
businessData.set("cashier_type", "1"); // 收银台模板
businessData.set("return_url", reqVO.getReturnUrl());
businessData.set("notifyurl", wpgjPayProperties.getNotifyUrl());
// 2. 构建加密请求数据 - 按照demo标准
String encryptedRequest = WpgjCryptoUtils.buildEncryptedRequest(
businessData,
wpgjPayProperties.getOrganizNo(),
wpgjPayProperties.getPublicKey()
);
// 3. URL编码 - 按照demo要求
String encodedRequest = java.net.URLEncoder.encode(encryptedRequest, "UTF-8");
log.info("[callWpgjApi] 发送加密请求数据: {}", encryptedRequest);
log.info("[callWpgjApi] 请求URL: {}", wpgjPayProperties.getApiUrl());
// 4. 发送HTTP请求 - 按照demo方式
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(), response.body());
throw exception(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, orderExtension.getNo());
} else {
// 处理错误响应
return parseWpgjError(responseJson, orderExtension.getNo());
}
} catch (Exception e) {
log.error("[callWpgjApi] WPGJ API调用异常,订单号: {}", orderExtension.getNo(), e);
throw exception(PAY_ORDER_SUBMIT_CHANNEL_ERROR, "API_ERROR", "支付网关调用异常: " + e.getMessage());
}
}
/**
* 解析WPGJ API成功响应 - 根据demo动态支付接口
*/
private PayOrderRespDTO parseWpgjApiResponse(JSONObject response, String outTradeNo) {
PayOrderRespDTO respDTO = new PayOrderRespDTO();
respDTO.setOutTradeNo(outTradeNo);
respDTO.setChannelCode("wpgj_dynamic");
respDTO.setStatus(PayOrderStatusEnum.WAITING.getStatus());
// 解析data字段中的payUrl(动态支付URL)
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);
}
respDTO.setRawData(JSONUtil.toJsonStr(response));
return respDTO;
}
/**
* 解析WPGJ API错误响应
*/
private PayOrderRespDTO parseWpgjError(JSONObject response, String outTradeNo) {
PayOrderRespDTO respDTO = new PayOrderRespDTO();
respDTO.setOutTradeNo(outTradeNo);
respDTO.setChannelCode("wpgj_dynamic");
respDTO.setStatus(PayOrderStatusEnum.CLOSED.getStatus()); // 错误时设置为关闭状态
// 设置错误信息
respDTO.setChannelErrorCode(response.getStr("code"));
respDTO.setChannelErrorMsg(response.getStr("msg"));
respDTO.setRawData(JSONUtil.toJsonStr(response));
log.error("[parseWpgjError] WPGJ API返回错误: code={}, msg={}",
response.getStr("code"), response.getStr("msg"));
return respDTO;
}
private PayOrderDO validateOrderCanSubmit(Long id) { private PayOrderDO validateOrderCanSubmit(Long id) {
PayOrderDO order = payOrderMapper.selectById(id); PayOrderDO order = payOrderMapper.selectById(id);
...@@ -405,11 +237,6 @@ public class PayOrderServiceImpl implements PayOrderService { ...@@ -405,11 +237,6 @@ public class PayOrderServiceImpl implements PayOrderService {
// 情况二:调用三方接口,查询支付单状态,是不是已支付 // 情况二:调用三方接口,查询支付单状态,是不是已支付
PayClient<?> payClient = channelService.getPayClient(orderExtension.getChannelId()); PayClient<?> payClient = channelService.getPayClient(orderExtension.getChannelId());
if (payClient == null) { if (payClient == null) {
// WPGJ特殊渠道,没有PayClient,跳过查询
if (orderExtension.getChannelId() == -1L && "wpgj_dynamic".equals(orderExtension.getChannelCode())) {
log.debug("[validateOrderCanSubmit][WPGJ渠道({}) 跳过支付状态查询]", orderExtension.getChannelId());
return;
}
log.error("[validateOrderCanSubmit][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId()); log.error("[validateOrderCanSubmit][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId());
return; return;
} }
...@@ -447,132 +274,16 @@ public class PayOrderServiceImpl implements PayOrderService { ...@@ -447,132 +274,16 @@ public class PayOrderServiceImpl implements PayOrderService {
@Override @Override
public void notifyOrder(Long channelId, PayOrderRespDTO notify) { public void notifyOrder(Long channelId, PayOrderRespDTO notify) {
// WPGJ特殊渠道处理
if (channelId == -1L && "wpgj_dynamic".equals(notify.getChannelCode())) {
log.info("[notifyOrder][WPGJ特殊渠道] 处理支付通知: {}", JSONUtil.toJsonStr(notify));
getSelf().notifyWpgjOrder(notify);
return;
}
// 原有渠道处理
PayChannelDO channel = channelService.validPayChannel(channelId); PayChannelDO channel = channelService.validPayChannel(channelId);
// 更新支付订单为已支付 // 更新支付订单为已支付
TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyOrder(channel, notify)); TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyOrder(channel, notify));
} }
/**
* 处理WPGJ旺铺聚合支付通知
*
* @param notify 支付结果通知
*/
@Transactional(rollbackFor = Exception.class)
public void notifyWpgjOrder(PayOrderRespDTO notify) {
log.info("[notifyWpgjOrder] 开始处理WPGJ支付通知: {}", JSONUtil.toJsonStr(notify));
// 情况一:支付成功的回调
if (PayOrderStatusEnum.isSuccess(notify.getStatus())) {
notifyWpgjOrderSuccess(notify);
return;
}
// 情况二:支付失败的回调
if (PayOrderStatusEnum.isClosed(notify.getStatus())) {
notifyWpgjOrderClosed(notify);
}
// 情况三:WAITING:无需处理
log.info("[notifyWpgjOrder] WPGJ支付通知处理完成,状态: {}", notify.getStatus());
}
/**
* 处理WPGJ支付成功通知
*/
private void notifyWpgjOrderSuccess(PayOrderRespDTO notify) {
// 1. 更新 PayOrderExtensionDO 支付成功
PayOrderExtensionDO orderExtension = updateOrderSuccess(notify);
// 2. 更新 PayOrderDO 支付成功(使用WPGJ特殊渠道参数)
updateWpgjOrderSuccess(orderExtension, notify);
// 3. 插入支付通知记录
notifyService.createPayNotifyTask(PayNotifyTypeEnum.ORDER.getType(),
orderExtension.getOrderId());
log.info("[notifyWpgjOrderSuccess] WPGJ支付成功通知处理完成,订单ID: {}", orderExtension.getOrderId());
}
/**
* 处理WPGJ支付关闭通知
*/
private void notifyWpgjOrderClosed(PayOrderRespDTO notify) {
updateWpgjOrderClosed(notify);
log.info("[notifyWpgjOrderClosed] WPGJ支付关闭通知处理完成");
}
/**
* 更新WPGJ PayOrderDO 支付成功
*/
private void updateWpgjOrderSuccess(PayOrderExtensionDO orderExtension, PayOrderRespDTO notify) {
// 1. 判断 PayOrderDO 是否处于待支付
PayOrderDO order = payOrderMapper.selectById(orderExtension.getOrderId());
if (order == null) {
throw exception(PAY_ORDER_NOT_FOUND);
}
if (PayOrderStatusEnum.isSuccess(order.getStatus()) // 如果已经是成功,直接返回,不用重复更新
&& Objects.equals(order.getExtensionId(), orderExtension.getId())) {
log.info("[updateWpgjOrderSuccess][order({}) 已经是已支付,无需更新]", order.getId());
return;
}
if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付
throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
}
// 2. 更新 PayOrderDO(使用WPGJ固定参数)
int updateCounts = payOrderMapper.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(),
PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus())
.channelId(-1L).channelCode("wpgj_dynamic")
.successTime(notify.getSuccessTime()).extensionId(orderExtension.getId()).no(orderExtension.getNo())
.channelOrderNo(notify.getChannelOrderNo()).channelUserId(notify.getChannelUserId())
.channelFeeRate(0.0) // WPGJ手续费暂时设为0
.channelFeePrice(0)
.build());
if (updateCounts == 0) { // 校验状态,必须是待支付
throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
}
log.info("[updateWpgjOrderSuccess][order({}) 更新为已支付]", order.getId());
}
/**
* 更新WPGJ PayOrderExtensionDO 关闭
*/
private void updateWpgjOrderClosed(PayOrderRespDTO notify) {
// 1. 查询 PayOrderExtensionDO
PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getOutTradeNo());
if (orderExtension == null) {
log.warn("[updateWpgjOrderClosed] 找不到PayOrderExtensionDO,订单号: {}", notify.getOutTradeNo());
return;
}
if (PayOrderStatusEnum.isClosed(orderExtension.getStatus())) { // 如果已经是关闭,直接返回,不用重复更新
log.info("[updateWpgjOrderClosed][orderExtension({}) 已经是支付关闭,无需更新]", orderExtension.getId());
return;
}
// 一般出现先是支付成功,然后支付关闭,都是全部退款导致关闭的场景。这个情况,我们不更新支付拓展单,只通过退款流程,更新支付单
if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) {
log.info("[updateWpgjOrderClosed][orderExtension({}) 是已支付,无需更新为支付关闭]", orderExtension.getId());
return;
}
if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态,必须是待支付
log.warn("[updateWpgjOrderClosed][orderExtension({}) 状态不是待支付,无需更新: {}", orderExtension.getId(), orderExtension.getStatus());
return;
}
// 2. 更新 PayOrderExtensionDO
int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), orderExtension.getStatus(),
PayOrderExtensionDO.builder().status(PayOrderStatusEnum.CLOSED.getStatus()).channelNotifyData(toJsonString(notify))
.channelErrorCode(notify.getChannelErrorCode()).channelErrorMsg(notify.getChannelErrorMsg()).build());
if (updateCounts == 0) { // 校验状态,必须是待支付
log.warn("[updateWpgjOrderClosed][orderExtension({}) 更新失败,可能状态已变更]", orderExtension.getId());
return;
}
log.info("[updateWpgjOrderClosed][orderExtension({}) 更新为支付关闭]", orderExtension.getId());
}
/** /**
* 通知并更新订单的支付结果 * 通知并更新订单的支付结果
* *
......
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