Commit fa76472d by Jony.L

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

parent 26ab13ea
package com.luhu.computility.module.compute.controller.admin.resourceorder; package com.luhu.computility.module.compute.controller.admin.resourceorder;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.luhu.computility.framework.common.exception.ServiceException; 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.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.impl.wpgj.WpgjPayProperties;
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;
import com.luhu.computility.module.compute.enums.ResourceOrderStatus; import com.luhu.computility.module.compute.enums.ResourceOrderStatus;
...@@ -11,6 +19,8 @@ import io.swagger.v3.oas.annotations.Parameter; ...@@ -11,6 +19,8 @@ import io.swagger.v3.oas.annotations.Parameter;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
import com.luhu.computility.framework.excel.core.util.ExcelUtils; import com.luhu.computility.framework.excel.core.util.ExcelUtils;
...@@ -32,6 +42,7 @@ import org.springframework.validation.annotation.Validated; ...@@ -32,6 +42,7 @@ import org.springframework.validation.annotation.Validated;
import static com.luhu.computility.framework.common.pojo.CommonResult.success; import static com.luhu.computility.framework.common.pojo.CommonResult.success;
import com.luhu.computility.framework.common.util.object.BeanUtils; import com.luhu.computility.framework.common.util.object.BeanUtils;
import com.luhu.computility.framework.tenant.core.aop.TenantIgnore;
@Tag(name = "管理后台 - 算力资源订单") @Tag(name = "管理后台 - 算力资源订单")
@RestController @RestController
...@@ -43,6 +54,12 @@ public class ResourceOrderController { ...@@ -43,6 +54,12 @@ public class ResourceOrderController {
@Resource @Resource
private ResourceOrderService resourceOrderService; private ResourceOrderService resourceOrderService;
@Resource
private PayOrderService payOrderService;
@Resource
private WpgjPayProperties wpgjPayProperties;
/** /**
* 内部支付任务回调 * 内部支付任务回调
*/ */
...@@ -55,6 +72,149 @@ public class ResourceOrderController { ...@@ -55,6 +72,149 @@ 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
package com.luhu.computility.module.compute.enums;
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
...@@ -485,7 +485,7 @@ public class ResourceOrderServiceImpl implements ResourceOrderService { ...@@ -485,7 +485,7 @@ public class ResourceOrderServiceImpl implements ResourceOrderService {
payOrderCreateReqDTO.setPrice(order.getPaymentPrice().intValue()) payOrderCreateReqDTO.setPrice(order.getPaymentPrice().intValue())
.setExpireTime(addTime(resourceOrderProperties.getPayExpireTime())); .setExpireTime(addTime(resourceOrderProperties.getPayExpireTime()));
Long payOrderId = payOrderApi.createOrder(payOrderCreateReqDTO); Long payOrderId = payOrderApi.createOrderWpgj(payOrderCreateReqDTO);
// 2. 提交支付订单(使用旺铺聚合支付) // 2. 提交支付订单(使用旺铺聚合支付)
AppPayOrderSubmitReqVO submitReqDTO = new AppPayOrderSubmitReqVO(); AppPayOrderSubmitReqVO submitReqDTO = new AppPayOrderSubmitReqVO();
......
...@@ -22,6 +22,14 @@ public interface PayOrderApi { ...@@ -22,6 +22,14 @@ public interface PayOrderApi {
Long createOrder(@Valid PayOrderCreateReqDTO reqDTO); Long createOrder(@Valid PayOrderCreateReqDTO reqDTO);
/** /**
* 创建WPGJ旺铺聚合支付单
*
* @param reqDTO 创建请求
* @return 支付单编号
*/
Long createOrderWpgj(@Valid PayOrderCreateReqDTO reqDTO);
/**
* 获得支付单 * 获得支付单
* *
* @param id 支付单编号 * @param id 支付单编号
......
...@@ -26,6 +26,11 @@ public class PayOrderApiImpl implements PayOrderApi { ...@@ -26,6 +26,11 @@ public class PayOrderApiImpl implements PayOrderApi {
} }
@Override @Override
public Long createOrderWpgj(PayOrderCreateReqDTO reqDTO) {
return payOrderService.createOrderWpgj(reqDTO);
}
@Override
public PayOrderRespDTO getOrder(Long id) { public PayOrderRespDTO getOrder(Long id) {
PayOrderDO order = payOrderService.getOrder(id); PayOrderDO order = payOrderService.getOrder(id);
return PayOrderConvert.INSTANCE.convert2(order); return PayOrderConvert.INSTANCE.convert2(order);
......
...@@ -21,6 +21,7 @@ import com.luhu.computility.module.pay.service.notify.PayNotifyService; ...@@ -21,6 +21,7 @@ import com.luhu.computility.module.pay.service.notify.PayNotifyService;
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.refund.PayRefundService; import com.luhu.computility.module.pay.service.refund.PayRefundService;
import com.luhu.computility.module.pay.service.transfer.PayTransferService; import com.luhu.computility.module.pay.service.transfer.PayTransferService;
import com.luhu.computility.module.pay.framework.pay.core.client.impl.wpgj.WpgjPayProperties;
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;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
......
package com.luhu.computility.module.pay.controller.admin.notify; package com.luhu.computility.module.pay.controller.admin.notify;
import com.luhu.computility.framework.common.pojo.CommonResult; import cn.hutool.core.util.StrUtil;
import com.luhu.computility.framework.common.util.json.JsonUtils; 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.dto.order.PayOrderRespDTO;
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.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 cn.hutool.json.JSONUtil;
import cn.hutool.json.JSONObject;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*; 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 javax.annotation.Resource;
import org.springframework.web.bind.annotation.RequestBody;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map; import java.util.Map;
import static com.luhu.computility.framework.common.pojo.CommonResult.success;
/** /**
* WPGJ旺铺聚合支付回调Controller * WPGJ旺铺聚合支付回调Controller
* *
...@@ -25,7 +29,7 @@ import static com.luhu.computility.framework.common.pojo.CommonResult.success; ...@@ -25,7 +29,7 @@ import static com.luhu.computility.framework.common.pojo.CommonResult.success;
*/ */
@Tag(name = "管理后台 - WPGJ旺铺聚合支付回调") @Tag(name = "管理后台 - WPGJ旺铺聚合支付回调")
@RestController @RestController
@RequestMapping("/admin-api/pay/wpgj") @RequestMapping("/pay/wpgj")
@Slf4j @Slf4j
public class WpgjPayController { public class WpgjPayController {
...@@ -36,47 +40,140 @@ public class WpgjPayController { ...@@ -36,47 +40,140 @@ public class WpgjPayController {
private WpgjPayProperties wpgjPayProperties; private WpgjPayProperties wpgjPayProperties;
@PostMapping("/notify") @PostMapping("/notify")
@PermitAll
@Operation(summary = "WPGJ支付异步回调通知") @Operation(summary = "WPGJ支付异步回调通知")
public CommonResult<String> notifyWpgjPay(@RequestBody String encryptedData) { public WpgjPayNotifyRespDTO notifyWpgjPay(@RequestBody WpgjPayNotifyDTO notifyDTO) {
try { WpgjPayNotifyRespDTO response = new WpgjPayNotifyRespDTO();
log.info("[notifyWpgjPay] 收到WPGJ支付回调,加密数据: {}", encryptedData);
// 1. 解密回调数据 try {
JSONObject decryptedResponse = WpgjCryptoUtils.decryptResponse(encryptedData, wpgjPayProperties.getPrivateKey()); log.info("[notifyWpgjPay] 收到WPGJ支付回调: {}", JsonUtils.toJsonString(notifyDTO));
Map<String, Object> decryptedData = decryptedResponse.get("data") != null ?
(Map<String, Object>) decryptedResponse.get("data") : decryptedResponse; // 1. 验证签名
if (!verifyWpgjSignature(notifyDTO)) {
log.info("[notifyWpgjPay] 解密后的回调数据: {}", JsonUtils.toJsonString(decryptedData)); log.error("[notifyWpgjPay] WPGJ回调签名验证失败");
response.setCode("99");
response.setMsg("签名验证失败");
response.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
return response;
}
// 2. 解析回调数据,构造PayOrderRespDTO // 2. 解析回调数据,构造PayOrderRespDTO
PayOrderRespDTO notify = parseWpgjNotifyData(decryptedData); PayOrderRespDTO notify = parseWpgjNotifyData(notifyDTO);
// 3. 处理支付结果通知 // 3. 处理支付结果通知
// 由于WPGJ是特殊渠道,直接调用内部处理方法 // 由于WPGJ是特殊渠道,直接调用内部处理方法
payOrderService.notifyOrder(-1L, notify); // 使用-1表示WPGJ特殊渠道 payOrderService.notifyOrder(-1L, notify); // 使用-1表示WPGJ特殊渠道
log.info("[notifyWpgjPay] WPGJ支付回调处理完成"); log.info("[notifyWpgjPay] WPGJ支付回调处理完成");
return success("success"); response.setCode("00");
response.setMsg("成功");
response.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
return response;
} catch (Exception e) { } catch (Exception e) {
log.error("[notifyWpgjPay] WPGJ支付回调处理失败", e); log.error("[notifyWpgjPay] WPGJ支付回调处理失败", e);
return success("fail"); 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 * 解析WPGJ回调数据为PayOrderRespDTO
*/ */
private PayOrderRespDTO parseWpgjNotifyData(Map<String, Object> data) { private PayOrderRespDTO parseWpgjNotifyData(WpgjPayNotifyDTO notifyDTO) {
PayOrderRespDTO notify = new PayOrderRespDTO(); PayOrderRespDTO notify = new PayOrderRespDTO();
// 根据WPGJ回调文档解析字段 // 根据WPGJ回调文档解析字段
notify.setOutTradeNo((String) data.get("mer_order_id")); // 商户订单号 notify.setOutTradeNo(notifyDTO.getMerOrderId()); // 商户订单号
notify.setChannelOrderNo((String) data.get("transaction_id")); // 第三方交易号 notify.setChannelOrderNo(notifyDTO.getTradeNo()); // 第三方交易号
notify.setStatus(parseWpgjStatus((String) data.get("trade_state"))); // 交易状态 notify.setStatus(parseWpgjStatus(notifyDTO.getOrderStatus())); // 交易状态
// 设置支付成功时间 // 设置支付成功时间
if ("SUCCESS".equals(data.get("trade_state"))) { if (WpgjOrderStatusEnum.isSuccess(notifyDTO.getOrderStatus())) {
notify.setSuccessTime(java.time.LocalDateTime.now()); notify.setSuccessTime(java.time.LocalDateTime.now());
} }
...@@ -84,7 +181,7 @@ public class WpgjPayController { ...@@ -84,7 +181,7 @@ public class WpgjPayController {
notify.setChannelCode("wpgj_dynamic"); notify.setChannelCode("wpgj_dynamic");
notify.setChannelErrorCode(null); // 成功时没有错误码 notify.setChannelErrorCode(null); // 成功时没有错误码
notify.setChannelErrorMsg(null); notify.setChannelErrorMsg(null);
notify.setRawData(JsonUtils.toJsonString(data)); // 保存原始数据 notify.setRawData(JsonUtils.toJsonString(notifyDTO)); // 保存原始数据
return notify; return notify;
} }
...@@ -92,10 +189,10 @@ public class WpgjPayController { ...@@ -92,10 +189,10 @@ public class WpgjPayController {
/** /**
* 解析WPGJ交易状态 * 解析WPGJ交易状态
*/ */
private Integer parseWpgjStatus(String tradeState) { private Integer parseWpgjStatus(String orderStatus) {
if ("SUCCESS".equals(tradeState)) { if (WpgjOrderStatusEnum.isSuccess(orderStatus)) { // 1-成功
return com.luhu.computility.module.pay.enums.order.PayOrderStatusEnum.SUCCESS.getStatus(); return com.luhu.computility.module.pay.enums.order.PayOrderStatusEnum.SUCCESS.getStatus();
} else if ("CLOSED".equals(tradeState) || "REVOKED".equals(tradeState)) { } else if (WpgjOrderStatusEnum.isFailedOrClosed(orderStatus)) { // 0-处理中 2-失败
return com.luhu.computility.module.pay.enums.order.PayOrderStatusEnum.CLOSED.getStatus(); return com.luhu.computility.module.pay.enums.order.PayOrderStatusEnum.CLOSED.getStatus();
} else { } else {
return com.luhu.computility.module.pay.enums.order.PayOrderStatusEnum.WAITING.getStatus(); return com.luhu.computility.module.pay.enums.order.PayOrderStatusEnum.WAITING.getStatus();
......
package com.luhu.computility.module.pay.controller.admin.notify.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.pay.controller.admin.notify.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
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
...@@ -53,4 +53,9 @@ public class WpgjPayProperties { ...@@ -53,4 +53,9 @@ public class WpgjPayProperties {
* 私钥 * 私钥
*/ */
private String privateKey; private String privateKey;
/**
* 签名密钥(用于回调验证)
*/
private String signKey;
} }
\ No newline at end of file
...@@ -83,6 +83,14 @@ public interface PayOrderService { ...@@ -83,6 +83,14 @@ public interface PayOrderService {
Long createOrder(@Valid PayOrderCreateReqDTO reqDTO); Long createOrder(@Valid PayOrderCreateReqDTO reqDTO);
/** /**
* 创建WPGJ旺铺聚合支付单
*
* @param reqDTO 创建请求
* @return 支付单编号
*/
Long createOrderWpgj(@Valid PayOrderCreateReqDTO reqDTO);
/**
* 提交支付 * 提交支付
* 此时,会发起支付渠道的调用 * 此时,会发起支付渠道的调用
* *
......
...@@ -144,6 +144,32 @@ public class PayOrderServiceImpl implements PayOrderService { ...@@ -144,6 +144,32 @@ public class PayOrderServiceImpl implements PayOrderService {
return order.getId(); return order.getId();
} }
@Override
public Long createOrderWpgj(PayOrderCreateReqDTO reqDTO) {
// 校验 App
PayAppDO app = appService.validPayApp(reqDTO.getAppKey());
// 查询对应的支付交易单是否已经存在。如果是,则直接返回
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 ,并校验其是否存在
...@@ -275,13 +301,13 @@ public class PayOrderServiceImpl implements PayOrderService { ...@@ -275,13 +301,13 @@ public class PayOrderServiceImpl implements PayOrderService {
} }
String responseData = response.body(); String responseData = response.body();
log.info("[callWpgjApi] WPGJ API调用成功,响应: {}", responseData);
// 6. 解析响应结果 - 需要解密 // 6. 解析响应结果 - 需要解密
JSONObject responseJson = new JSONObject(responseData); JSONObject responseJson = new JSONObject(responseData);
if ("0000".equals(responseJson.getStr("code"))) { if ("0000".equals(responseJson.getStr("code"))) {
// 解密响应数据 // 解密响应数据
JSONObject decryptedResponse = WpgjCryptoUtils.decryptResponse(responseData, wpgjPayProperties.getPrivateKey()); JSONObject decryptedResponse = WpgjCryptoUtils.decryptResponse(responseData, wpgjPayProperties.getPrivateKey());
log.info("[callWpgjApi] WPGJ API解密后的完整响应: {}", decryptedResponse);
return parseWpgjApiResponse(decryptedResponse, orderExtension.getNo()); return parseWpgjApiResponse(decryptedResponse, orderExtension.getNo());
} else { } else {
// 处理错误响应 // 处理错误响应
......
...@@ -175,7 +175,8 @@ computility: ...@@ -175,7 +175,8 @@ computility:
mer-code: K20241200111267 mer-code: K20241200111267
term-code: 1011215692596 term-code: 1011215692596
api-url: https://stg5-qr.wpgjcs.com/industrial/payment/dynamic api-url: https://stg5-qr.wpgjcs.com/industrial/payment/dynamic
notify-url: https://phslgld.hnluchuan.com/admin-api/pay/wpgj/notify notify-url: https://phslgld.hnluchuan.com/admin-api/compute/resource-order/pay/wpgj-notify
sign-key: 07714583f82b4db8b675b32cd5e0969743 # WPGJ回调签名密钥(测试用,实际需要向WPGJ确认)
public-key: | public-key: |
-----BEGIN PUBLIC KEY----- -----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvS43wZPiULrGVcXLIsTRcPZn1ZH3P5DN/IxdWUTijPMkruLu36Q1Dh+8x/oGnQBJFckp31YTptyXa2CxC7wxodAc7I4umDp1h43vDFItsPb0DBDtYUTdjw7YFBppwgyr8soUUfc9fDShgYbza/pqOzsvgXECubKrtpeBii0BGShfs20H/Rawzn2WXMUq5l+Cw7pXwJ87FT8rbJ/KJRmtMTiy5yczWikBETCGv/b7Wxw10c7w0NBeWW/vjj6FoJQOcoo3QEcjPAAx0pC4HBZiE4T3ouvsWjxyhq2CZ6FFVT05jPezw2/FZI26DOKm3pStXne3gsXzcuxPGIrzccrgjQIDAQAB MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvS43wZPiULrGVcXLIsTRcPZn1ZH3P5DN/IxdWUTijPMkruLu36Q1Dh+8x/oGnQBJFckp31YTptyXa2CxC7wxodAc7I4umDp1h43vDFItsPb0DBDtYUTdjw7YFBppwgyr8soUUfc9fDShgYbza/pqOzsvgXECubKrtpeBii0BGShfs20H/Rawzn2WXMUq5l+Cw7pXwJ87FT8rbJ/KJRmtMTiy5yczWikBETCGv/b7Wxw10c7w0NBeWW/vjj6FoJQOcoo3QEcjPAAx0pC4HBZiE4T3ouvsWjxyhq2CZ6FFVT05jPezw2/FZI26DOKm3pStXne3gsXzcuxPGIrzccrgjQIDAQAB
......
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