Commit 7bc08650 by renyizhao

已经可以支付 但轮询还未测试

parent fde4e3ac
......@@ -33,6 +33,11 @@
<artifactId>computility-module-infra</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.luhu</groupId>
<artifactId>computility-module-pay</artifactId>
<version>${revision}</version>
</dependency>
<!-- 业务组件 -->
<dependency>
......
package com.luhu.computility.module.member.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.member.service.recharge.MemberRechargeService;
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.WpgjOrderStatusEnum;
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.tags.Tag;
import lombok.RequiredArgsConstructor;
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 java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
/**
* 会员模块 - WPGJ旺铺聚合支付回调Controller
*
* 处理会员充值的支付回调
*
* @author jony
*/
@Tag(name = "管理后台 - 会员充值WPGJ旺铺聚合支付回调")
@RestController
@RequestMapping("/member/wpgj")
@Slf4j
public class MemberRechargeWpgjPayController {
@Resource
private MemberRechargeService memberRechargeService;
@Resource
private WpgjPayProperties wpgjPayProperties;
@PostMapping("/notify")
@PermitAll
@Operation(summary = "WPGJ支付异步回调通知 - 会员充值")
public WpgjPayNotifyRespDTO notifyWpgjPay(@RequestBody WpgjPayNotifyDTO notifyDTO) {
WpgjPayNotifyRespDTO response = new WpgjPayNotifyRespDTO();
try {
log.info("[notifyWpgjPay][Member] 收到WPGJ支付回调: {}", JsonUtils.toJsonString(notifyDTO));
// 1. 验证签名
if (!verifyWpgjSignature(toPayloadMap(notifyDTO))) {
log.error("[notifyWpgjPay][Member] WPGJ回调签名验证失败");
response.setCode("99");
response.setMsg("签名验证失败");
response.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
return response;
}
String merOrderId = notifyDTO.getMerOrderId();
String orderStatus = notifyDTO.getOrderStatus();
log.info("[notifyWpgjPay][Member] 处理WPGJ支付结果,商户订单号: {}, 订单状态: {}", merOrderId, orderStatus);
// 2. 根据订单状态更新充值记录
if (WpgjOrderStatusEnum.isSuccess(orderStatus)) {
// 支付成功:更新充值状态为已支付
memberRechargeService.updateRechargeStatus(Long.parseLong(merOrderId), 1);
log.info("[notifyWpgjPay][Member] 支付成功处理完成,商户订单号: {}", merOrderId);
} else if (WpgjOrderStatusEnum.isFailedOrClosed(orderStatus)) {
// 支付失败/关闭:更新充值状态为支付失败
memberRechargeService.updateRechargeStatus(Long.parseLong(merOrderId), -1);
log.info("[notifyWpgjPay][Member] 支付失败/关闭处理完成,商户订单号: {}", merOrderId);
} else {
log.info("[notifyWpgjPay][Member] 订单状态无需处理,商户订单号: {}, 状态: {}", merOrderId, orderStatus);
}
// 3. 返回成功应答
response.setCode("00");
response.setMsg("成功");
response.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
log.info("[notifyWpgjPay][Member] 返回wpgj参数: {}", JsonUtils.toJsonString(response));
return response;
} catch (Exception e) {
log.error("[notifyWpgjPay][Member] WPGJ支付回调处理失败", e);
response.setCode("99");
response.setMsg("处理失败");
response.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
return response;
}
}
/**
* Map 形式的回调请求验签:对所有出现的非空字段(排除 sign)进行 ASCII 排序拼接后 + key,再 MD5(UTF-8) 大写
*/
private boolean verifyWpgjSignature(Map<String, Object> payload) {
try {
Object signObj = payload.get("sign");
String originSign = signObj == null ? null : String.valueOf(signObj);
if (StrUtil.isBlank(originSign)) {
log.warn("[verifyWpgjSignature][Member] 回调缺少 sign 字段");
return false;
}
// 过滤掉 sign、自身为空的字段,按 ASCII 升序
TreeMap<String, String> sorted = new TreeMap<>();
for (Map.Entry<String, Object> e : payload.entrySet()) {
String k = e.getKey();
if ("sign".equals(k)) {
continue;
}
String v = e.getValue() == null ? null : String.valueOf(e.getValue());
if (StrUtil.isBlank(v)) {
continue;
}
sorted.put(k, v);
}
String dataStr = sorted.entrySet().stream()
.map(en -> en.getKey() + "=" + en.getValue())
.collect(Collectors.joining("&"));
String signStr = dataStr + "&key=" + wpgjPayProperties.getSignKey();
String calculatedSign = cn.hutool.crypto.digest.DigestUtil.md5Hex(signStr).toUpperCase();
log.info("[verifyWpgjSignature][Member-New] 待签名字符串: {}", dataStr);
log.info("[verifyWpgjSignature][Member-New] 加签前(带key): {}", signStr);
log.info("[verifyWpgjSignature][Member-New] 计算签名: {},原始签名: {}", calculatedSign, originSign);
return calculatedSign.equalsIgnoreCase(originSign);
} catch (Exception e) {
log.error("[verifyWpgjSignature][Member-New] 验签异常", e);
return false;
}
}
/**
* 将 DTO 转换为 Map(以供应商字段名 snake_case 作为 key),便于统一验签
*/
private Map<String, Object> toPayloadMap(WpgjPayNotifyDTO dto) {
TreeMap<String, Object> map = new TreeMap<>();
map.put("device_no", dto.getDeviceNo());
map.put("mer_no", dto.getMerNo());
map.put("mer_code", dto.getMerCode());
map.put("payway_code", dto.getPaywayCode());
map.put("order_id", dto.getOrderId());
map.put("mer_order_id", dto.getMerOrderId());
map.put("gateway_mer_order_id", dto.getGatewayMerOrderId());
map.put("order_time", dto.getOrderTime());
map.put("order_amt", dto.getOrderAmt());
map.put("order_status", dto.getOrderStatus());
map.put("trade_no", dto.getTradeNo());
map.put("trade_time", dto.getTradeTime());
map.put("order_title", dto.getOrderTitle());
map.put("fee", dto.getFee());
map.put("act_amt", dto.getActAmt());
map.put("buyer_id", dto.getBuyerId());
map.put("trade_top_no", dto.getTradeTopNo());
map.put("card_type", dto.getCardType());
map.put("sign", dto.getSign());
return map;
}
}
......@@ -3,7 +3,11 @@ package com.luhu.computility.module.member.controller.app.recharge;
import com.luhu.computility.framework.common.pojo.CommonResult;
import com.luhu.computility.module.member.controller.admin.recharge.vo.MemberRechargeCreateReqVO;
import com.luhu.computility.module.member.controller.admin.recharge.vo.MemberRechargeRespVO;
import com.luhu.computility.module.member.dal.dataobject.recharge.MemberRechargeDO;
import com.luhu.computility.module.member.service.recharge.MemberRechargeService;
import com.luhu.computility.module.pay.api.wpgj.CommonWpgjPayApi;
import com.luhu.computility.module.pay.api.wpgj.dto.CommonWpgjCreateReqDTO;
import com.luhu.computility.module.pay.api.wpgj.vo.CommonWpgjPayOrderSubmitRespVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
......@@ -25,6 +29,12 @@ import static com.luhu.computility.framework.security.core.util.SecurityFramewor
public class AppRechargeController {
private final MemberRechargeService rechargeService;
private final CommonWpgjPayApi commonWpgjPayApi;
/**
* 业务类型:3-会员充值
*/
private static final int BUSINESS_TYPE_MEMBER_RECHARGE = 3;
@PostMapping("/create")
@Operation(summary = "创建充值记录")
......@@ -32,23 +42,84 @@ public class AppRechargeController {
Long userId = getLoginUserId();
BigDecimal amount = reqVO.getAmount();
BigDecimal quota = amount.setScale(2, RoundingMode.HALF_UP);
String transactionId = "R" + System.currentTimeMillis() + userId;
// 1. 先创建充值记录
String transactionId = "R" + System.currentTimeMillis() + userId;
Long rechargeId = rechargeService.createRecharge(userId, amount, quota, "wpgj", transactionId);
MemberRechargeRespVO respVO = new MemberRechargeRespVO();
respVO.setId(rechargeId);
respVO.setAmount(amount);
respVO.setQuota(quota);
respVO.setPayChannel("wpgj");
respVO.setTransactionId(transactionId);
respVO.setPayStatus(0);
respVO.setPayStatusName("待支付");
respVO.setQrCodeUrl("https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=recharge_" + rechargeId);
// 2. 调用旺铺聚合支付
try {
CommonWpgjCreateReqDTO createReqDTO = new CommonWpgjCreateReqDTO();
createReqDTO.setBusinessType(BUSINESS_TYPE_MEMBER_RECHARGE);
createReqDTO.setBusinessOrderId(rechargeId);
createReqDTO.setOrderTitle("会员充值 - " + amount + "元");
createReqDTO.setPayAmount(amount.multiply(BigDecimal.valueOf(100)).intValue()); // 转换为分
CommonWpgjPayOrderSubmitRespVO payRespVO = commonWpgjPayApi.createCommonWpgjOrder(createReqDTO);
// 3. 构建返回
MemberRechargeRespVO respVO = new MemberRechargeRespVO();
respVO.setId(rechargeId);
respVO.setAmount(amount);
respVO.setQuota(quota);
respVO.setPayChannel("wpgj");
respVO.setTransactionId(transactionId);
respVO.setPayStatus(0);
respVO.setPayStatusName("待支付");
// 根据 displayMode 决定二维码URL
if ("QR_CODE".equals(payRespVO.getDisplayMode()) && payRespVO.getDisplayContent() != null) {
respVO.setQrCodeUrl(payRespVO.getDisplayContent());
} else if (payRespVO.getDisplayContent() != null) {
respVO.setQrCodeUrl(payRespVO.getDisplayContent());
}
log.info("[创建充值记录] userId={}, amount={}, rechargeId={}", userId, amount, rechargeId);
log.info("[创建充值记录] userId={}, amount={}, rechargeId={}, wpgjOrderId={}", userId, amount, rechargeId, payRespVO.getWpgjOrderId());
return CommonResult.success(respVO);
} catch (Exception e) {
log.error("[创建充值记录] 调用旺铺支付失败 userId={}, amount={}, rechargeId={}", userId, amount, rechargeId, e);
// 返回基本信息,让前端显示错误
MemberRechargeRespVO respVO = new MemberRechargeRespVO();
respVO.setId(rechargeId);
respVO.setAmount(amount);
respVO.setQuota(quota);
respVO.setPayChannel("wpgj");
respVO.setTransactionId(transactionId);
respVO.setPayStatus(0);
respVO.setPayStatusName("待支付");
return CommonResult.success(respVO);
}
}
@GetMapping("/get")
@Operation(summary = "查询充值记录状态")
public CommonResult<MemberRechargeRespVO> getRecharge(@RequestParam("id") Long id) {
MemberRechargeDO recharge = rechargeService.getRechargeById(id);
if (recharge == null) {
return CommonResult.error(1, "充值记录不存在");
}
MemberRechargeRespVO respVO = new MemberRechargeRespVO();
respVO.setId(recharge.getId());
respVO.setAmount(recharge.getAmount());
respVO.setQuota(recharge.getQuota());
respVO.setPayChannel(recharge.getPayChannel());
respVO.setPayStatus(recharge.getPayStatus());
respVO.setPayStatusName(getPayStatusName(recharge.getPayStatus()));
respVO.setPayTime(recharge.getPayTime());
respVO.setTransactionId(recharge.getTransactionId());
return CommonResult.success(respVO);
}
private String getPayStatusName(Integer payStatus) {
if (payStatus == null) return "未知";
if (payStatus == -1) return "支付失败";
if (payStatus == 0) return "待支付";
if (payStatus == 1) return "已支付";
if (payStatus == 2) return "已退款";
return "未知";
}
}
......@@ -35,10 +35,20 @@ public class MemberRechargeServiceImpl implements MemberRechargeService {
2, "已退款"
);
/**
* 业务类型:3-会员充值(用于生成订单号)
*/
private static final int BUSINESS_TYPE_MEMBER_RECHARGE = 3;
@Override
public Long createRecharge(Long userId, BigDecimal amount, BigDecimal quota,
String payChannel, String transactionId) {
// 生成类似 compute 模块那样的长订单号:前缀(1位) + 时间戳(13位)
// 例如:3117804779539728
long orderId = Long.parseLong(BUSINESS_TYPE_MEMBER_RECHARGE + String.valueOf(System.currentTimeMillis()));
MemberRechargeDO recharge = MemberRechargeDO.builder()
.id(orderId)
.userId(userId)
.amount(amount)
.quota(quota)
......@@ -48,7 +58,7 @@ public class MemberRechargeServiceImpl implements MemberRechargeService {
.callbackStatus(0)
.build();
rechargeMapper.insert(recharge);
log.info("[创建充值记录] userId={}, amount={}, transactionId={}", userId, amount, transactionId);
log.info("[创建充值记录] userId={}, amount={}, transactionId={}, orderId={}", userId, amount, transactionId, orderId);
return recharge.getId();
}
......
......@@ -250,6 +250,12 @@ public class PayOrderApiImpl implements PayOrderApi {
return apiNotifyUrl;
}
break;
case 3:
String memberNotifyUrl = wpgjPayProperties.getNotifyUrlMember();
if (memberNotifyUrl != null && !memberNotifyUrl.trim().isEmpty()) {
return memberNotifyUrl;
}
break;
default:
break;
}
......
......@@ -50,6 +50,11 @@ public class WpgjPayProperties {
private String notifyUrlApi;
/**
* 会员充值模块回调地址
*/
private String notifyUrlMember;
/**
* 兼容旧的notifyUrl字段
*/
private String notifyUrl;
......
......@@ -211,6 +211,7 @@ computility:
-----END PRIVATE KEY-----
notify-url-compute: https://ric.admin.lijinqi.com/admin-api/compute/wpgj/notify # 算力资源模块WPGJ回调地址
notify-url-api: https://ric.admin.lijinqi.com/admin-api/apihub/wpgj/notify # APIHub模块WPGJ回调地址
notify-url-member: https://ric.admin.lijinqi.com/admin-api/member/wpgj/notify # 会员充值模块WPGJ回调地址
access-log: # 访问日志的配置项
enable: true
demo: false # 开启演示模式
......
......@@ -245,6 +245,7 @@ computility:
-----END PRIVATE KEY-----
notify-url-compute: https://ric.admin.lijinqi.com/admin-api/compute/wpgj/notify # 算力资源模块WPGJ回调地址
notify-url-api: https://ric.admin.lijinqi.com/admin-api/apihub/wpgj/notify # APIHub模块WPGJ回调地址
notify-url-member: https://ric.admin.lijinqi.com/admin-api/member/wpgj/notify # 会员充值模块WPGJ回调地址
access-log: # 访问日志的配置项
enable: false
demo: false # 关闭演示模式
......
......@@ -211,6 +211,7 @@ computility:
-----END PRIVATE KEY-----
notify-url-compute: https://phslgld.hnluchuan.com/admin-api/compute/wpgj/notify # 算力资源模块WPGJ回调地址
notify-url-api: https://phslgld.hnluchuan.com/admin-api/apihub/wpgj/notify # APIHub模块WPGJ回调地址
notify-url-member: https://phslgld.hnluchuan.com/admin-api/member/wpgj/notify # 会员充值模块WPGJ回调地址
access-log: # 访问日志的配置项
enable: true
demo: false # 开启演示模式
......
......@@ -283,6 +283,7 @@ computility:
- /open-api/external/**
- /admin-api/compute/wpgj/notify
- /admin-api/apihub/wpgj/notify
- /admin-api/member/wpgj/notify
ignore-visit-urls:
- /admin-api/system/user/profile/**
- /admin-api/system/auth/**
......
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