31 changed files with 749 additions and 94 deletions
@ -1,4 +1,4 @@ |
|||
package com.bnyer.pay.enums; |
|||
package com.bnyer.common.core.enums; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Getter; |
|||
@ -0,0 +1,84 @@ |
|||
package com.bnyer.order.listener.vip; |
|||
|
|||
import com.alibaba.fastjson.JSON; |
|||
import com.alibaba.fastjson.JSONObject; |
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|||
import com.bnyer.common.core.domain.VipOrder; |
|||
import com.bnyer.common.core.enums.EnumPayStatus; |
|||
import com.bnyer.common.core.enums.ResponseEnum; |
|||
import com.bnyer.common.core.exception.ServiceException; |
|||
import com.bnyer.common.rocketmq.config.RocketMqConstant; |
|||
import com.bnyer.order.enums.EnumVipOrderStatus; |
|||
import com.bnyer.order.mapper.VipOrderMapper; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.rocketmq.client.producer.SendStatus; |
|||
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; |
|||
import org.apache.rocketmq.spring.core.RocketMQListener; |
|||
import org.apache.rocketmq.spring.core.RocketMQTemplate; |
|||
import org.springframework.messaging.support.GenericMessage; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
import javax.annotation.Resource; |
|||
import java.util.Date; |
|||
import java.util.Objects; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/03/24 |
|||
* @description :订单支付回调mq消费监听 |
|||
*/ |
|||
@Slf4j |
|||
@Component |
|||
@RocketMQMessageListener(topic = RocketMqConstant.VIP_ORDER_PAY_NOTIFY_TOPIC,consumerGroup = RocketMqConstant.VIP_ORDER_PAY_NOTIFY_TOPIC) |
|||
public class VipOrderPayNotifyConsumer implements RocketMQListener<String> { |
|||
|
|||
@Resource |
|||
private VipOrderMapper vipOrderMapper; |
|||
|
|||
@Resource |
|||
private RocketMQTemplate vipRecordMqTemplate; |
|||
|
|||
@Override |
|||
public void onMessage(String message) { |
|||
log.info("收到消息:{}", message); |
|||
JSONObject jsonObject = JSON.parseObject(message); |
|||
//修改订单表状态为已支付
|
|||
String orderId = jsonObject.getString("orderId"); |
|||
VipOrder vipOrder = vipOrderMapper.selectOne(new LambdaQueryWrapper<VipOrder>().eq(VipOrder::getOrderId, orderId)); |
|||
if (Objects.isNull(vipOrder)){ |
|||
log.error("订单不存在,订单id:{}",orderId); |
|||
return; |
|||
} |
|||
vipOrder.setPayStatus(EnumPayStatus.SUCCESS.getCode()); |
|||
vipOrder.setOrderStatus(EnumVipOrderStatus.SUCCESS.getStatus()); |
|||
vipOrder.setUpdateTime(new Date()); |
|||
vipOrder.setPayTime(new Date()); |
|||
vipOrderMapper.updateById(vipOrder); |
|||
// TODO: 2023/04/13 发消息到img服务创建vip记录开通会员
|
|||
String msg = buildVipRecordMsg(vipOrder); |
|||
SendStatus sendStatus = vipRecordMqTemplate.syncSend(RocketMqConstant.VIP_RECORD_CREATE_TOPIC, new GenericMessage<>(msg)).getSendStatus(); |
|||
if (!Objects.equals(sendStatus, SendStatus.SEND_OK)) { |
|||
// 消息发不出去就抛异常
|
|||
throw new ServiceException(ResponseEnum.SERVER_ERROR); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 构建vip记录消息体 |
|||
* @param vipOrder |
|||
* @return |
|||
*/ |
|||
private String buildVipRecordMsg(VipOrder vipOrder) { |
|||
JSONObject jsonObject = new JSONObject(); |
|||
jsonObject.put("orderId",vipOrder.getOrderId()); |
|||
jsonObject.put("startTime",vipOrder.getStartTime()); |
|||
jsonObject.put("endTime",vipOrder.getEndTime()); |
|||
jsonObject.put("vipId",vipOrder.getVipId()); |
|||
jsonObject.put("vipName",vipOrder.getVipName()); |
|||
jsonObject.put("vipTypeName",vipOrder.getVipTypeName()); |
|||
jsonObject.put("categoryName",vipOrder.getCategoryName()); |
|||
jsonObject.put("phone",vipOrder.getPhone()); |
|||
jsonObject.put("userId",vipOrder.getUserId()); |
|||
return JSON.toJSONString(jsonObject); |
|||
} |
|||
} |
|||
@ -1,18 +0,0 @@ |
|||
//package com.bnyer.order.listener.vip;
|
|||
//
|
|||
//import com.bnyer.common.rocketmq.config.RocketMqConstant;
|
|||
//import lombok.extern.slf4j.Slf4j;
|
|||
//import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
|||
//import org.springframework.stereotype.Component;
|
|||
//
|
|||
///**
|
|||
// * @author :WXC
|
|||
// * @Date :2023/03/24
|
|||
// * @description :订单支付成功mq消费监听
|
|||
// */
|
|||
//@Slf4j
|
|||
//@Component
|
|||
//@RocketMQMessageListener(topic = RocketMqConstant.VIP_ORDER_PAY_SUCCESS_TOPIC,consumerGroup = RocketMqConstant.VIP_ORDER_PAY_SUCCESS_TOPIC)
|
|||
//public class VipOrderPaySuccessConsumer {
|
|||
//
|
|||
//}
|
|||
@ -0,0 +1,83 @@ |
|||
package com.bnyer.pay.design.strategy; |
|||
|
|||
import com.alibaba.fastjson.JSON; |
|||
import com.alipay.api.msg.MsgConstants; |
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|||
import com.bnyer.common.core.domain.PayInfo; |
|||
import com.bnyer.common.core.enums.EnumPayType; |
|||
import com.bnyer.pay.dto.PayNotifyCheckDto; |
|||
import com.bnyer.pay.mapper.PayInfoMapper; |
|||
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
|
|||
import javax.annotation.Resource; |
|||
import java.util.Objects; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/04/12 |
|||
* @description :提供公共的方法 |
|||
*/ |
|||
@Slf4j |
|||
public abstract class AbstractPayStrategy implements IPayStrategy { |
|||
|
|||
@Resource |
|||
private PayInfoMapper payInfoMapper; |
|||
|
|||
public <T> T payNotify(Object o,Class<T> tClass,EnumPayType payType){ |
|||
String string = ""; |
|||
if (EnumPayType.ALI_PAY == payType){ |
|||
string = o.toString(); |
|||
}else if (EnumPayType.WX_PAY == payType){ |
|||
|
|||
} |
|||
return JSON.parseObject(string,tClass); |
|||
} |
|||
|
|||
/** |
|||
* 校验是否已支付避免重复调用 |
|||
* @param checkDto |
|||
* @return |
|||
*/ |
|||
public String payNotifyCheck(PayNotifyCheckDto checkDto){ |
|||
String resultMsg = ""; |
|||
PayInfo payInfo = payInfoMapper.selectOne(new LambdaQueryWrapper<PayInfo>().eq(PayInfo::getPayId, checkDto.getPayId()).eq(PayInfo::getIsShow, "1")); |
|||
if (Objects.isNull(payInfo)){ |
|||
return WxPayNotifyResponse.fail("查询支付订单信息不存在"); |
|||
} |
|||
log.info("查询到支付订单信息为:{}", JSON.toJSONString(payInfo)); |
|||
//订单中的金额
|
|||
String payInfoPayAmount = payInfo.getPayAmount().toString(); |
|||
//回调中的金额
|
|||
String notifyPayAmount = checkDto.getPayAmount(); |
|||
//对账状态
|
|||
Integer singleStatus = payInfo.getSingleStatus(); |
|||
log.info("回调中的金额:{},订单中的金额:{}",payInfoPayAmount,notifyPayAmount); |
|||
if (payInfoPayAmount.equals(notifyPayAmount)){ |
|||
if (Objects.nonNull(singleStatus)){ |
|||
resultMsg = buildNotifyCheckResultMsg(checkDto.getPayType(),true,"OK"); |
|||
} |
|||
}else { |
|||
resultMsg = buildNotifyCheckResultMsg(checkDto.getPayType(),false,"Amount_Diff"); |
|||
} |
|||
return resultMsg; |
|||
} |
|||
|
|||
/** |
|||
* 构建校验后回调返回结果信息 |
|||
* @param payType 支付方式 |
|||
* @param isSuccess 是否构建成功消息 |
|||
* @param msg 消息内容 |
|||
* @return |
|||
*/ |
|||
private String buildNotifyCheckResultMsg(EnumPayType payType,boolean isSuccess,String msg){ |
|||
String result = ""; |
|||
if (EnumPayType.WX_PAY == payType){ |
|||
result = isSuccess?WxPayNotifyResponse.success(msg):WxPayNotifyResponse.fail(msg); |
|||
}else if (EnumPayType.ALI_PAY == payType){ |
|||
result = isSuccess?MsgConstants.SUCCESS:MsgConstants.FAIL; |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
package com.bnyer.pay.design.strategy; |
|||
|
|||
import com.bnyer.pay.dto.UnifiedOrderDto; |
|||
import com.bnyer.pay.vo.PayInOrderVo; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/04/03 |
|||
* @description :支付策略 |
|||
*/ |
|||
public interface IPayStrategy { |
|||
/** |
|||
* 统一下单 |
|||
* @param dto |
|||
* @return |
|||
*/ |
|||
PayInOrderVo unifiedOrder(UnifiedOrderDto dto); |
|||
|
|||
/** |
|||
* 支付回调处理 |
|||
* 满足支付成功需要几个条件: |
|||
* 1.回调到pay服务时要保证修改支付状态:payStatus为1001、对账状态:singStatus为1001 |
|||
* 2.pay服务处理完以后发送消息到order服务修改order表订单状态:orderStatus为1、支付状态:payStatus为1001 |
|||
* |
|||
* 如果pay服务发送消息失败可利用第三方支付回调的重试机制多次发送,如果order服务消费失败可利用mq重试机制重试消费,重试多次还是失败就人工处理该订单 |
|||
* @param params |
|||
* @return |
|||
*/ |
|||
String parsePayNotify(String params); |
|||
|
|||
//===========待完成================
|
|||
// TODO: 2023/04/03 订单查询
|
|||
// void orderQuery();
|
|||
|
|||
// TODO: 2023/04/03 退款
|
|||
// void refund();
|
|||
|
|||
// TODO: 2023/04/03 退款查询
|
|||
// void refundQuery();
|
|||
} |
|||
@ -1,32 +0,0 @@ |
|||
package com.bnyer.pay.design.strategy; |
|||
|
|||
import com.bnyer.pay.dto.UnifiedOrderDto; |
|||
import com.bnyer.pay.vo.PayInOrderVo; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/04/03 |
|||
* @description :支付策略 |
|||
*/ |
|||
public interface PayStrategy { |
|||
/** |
|||
* 统一下单 |
|||
* @param dto |
|||
* @return |
|||
*/ |
|||
PayInOrderVo unifiedOrder(UnifiedOrderDto dto); |
|||
|
|||
|
|||
//===========待完成================
|
|||
// TODO: 2023/04/03 回调处理
|
|||
// void parsePayNotify();
|
|||
|
|||
// TODO: 2023/04/03 订单查询
|
|||
// void orderQuery();
|
|||
|
|||
// TODO: 2023/04/03 退款
|
|||
// void refund();
|
|||
|
|||
// TODO: 2023/04/03 退款查询
|
|||
// void refundQuery();
|
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
package com.bnyer.pay.dto; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Getter; |
|||
import lombok.NoArgsConstructor; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/04/10 |
|||
* @description : |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
public class EditPayInfoNotifyDto { |
|||
|
|||
//appid
|
|||
private String appId; |
|||
//支付号
|
|||
private String payNo; |
|||
//业务系统支付订单号
|
|||
private String payId; |
|||
//交易状态:TRADE_FINISHED,TRADE_SUCCESS,TRADE_CLOSED
|
|||
private String tradeStatus; |
|||
//交易付款时间
|
|||
private String payTime; |
|||
//交易退款时间
|
|||
private String refundTime; |
|||
//订单金额(元)
|
|||
private String payAmount; |
|||
//支付类型
|
|||
private String payType; |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
package com.bnyer.pay.dto; |
|||
|
|||
import com.bnyer.common.core.enums.EnumPayType; |
|||
import lombok.Getter; |
|||
import lombok.NoArgsConstructor; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/04/13 |
|||
* @description : |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
@NoArgsConstructor |
|||
public class PayNotifyCheckDto { |
|||
|
|||
/** |
|||
* 支付方式 |
|||
*/ |
|||
private EnumPayType payType; |
|||
|
|||
/** |
|||
* 支付单号 |
|||
*/ |
|||
private String payId; |
|||
|
|||
/** |
|||
* 支付金额 |
|||
*/ |
|||
private String payAmount; |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
package com.bnyer.pay.utils; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.w3c.dom.Node; |
|||
import org.w3c.dom.NodeList; |
|||
|
|||
import javax.xml.parsers.DocumentBuilder; |
|||
import javax.xml.parsers.DocumentBuilderFactory; |
|||
import java.io.ByteArrayInputStream; |
|||
import java.io.InputStream; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/04/12 |
|||
* @description : |
|||
*/ |
|||
@Slf4j |
|||
public class WXPayUtil { |
|||
|
|||
/** |
|||
* XML格式字符串转换为Map |
|||
* |
|||
* @param strXML XML字符串 |
|||
* @return XML数据转换后的Map |
|||
* @throws Exception |
|||
*/ |
|||
public static Map<String, String> xmlToMap(String strXML) throws Exception { |
|||
Map<String, String> data = new HashMap<String, String>(); |
|||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); |
|||
try{ |
|||
//防止XXE攻击,即xml带入一个DTD。
|
|||
//解决方案1:这是优先选择. 如果不允许DTDs (doctypes) ,几乎可以阻止所有的XML实体攻击
|
|||
String FEATURE = "http://apache.org/xml/features/disallow-doctype-decl"; |
|||
documentBuilderFactory.setFeature(FEATURE, true); |
|||
|
|||
//解决方案2:如果不能完全禁用DTDs,最少采取以下措施
|
|||
// FEATURE = "http://xml.org/sax/features/external-general-entities";
|
|||
// documentBuilderFactory.setFeature(FEATURE, false);
|
|||
// FEATURE = "http://xml.org/sax/features/external-parameter-entities";
|
|||
// documentBuilderFactory.setFeature(FEATURE, false);
|
|||
// documentBuilderFactory.setXIncludeAware(false);
|
|||
// documentBuilderFactory.setExpandEntityReferences(false);
|
|||
}catch (Exception e){ |
|||
log.info("=============:微信支付XXE尝试攻击失败============"); |
|||
return data; |
|||
} |
|||
DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder(); |
|||
InputStream stream = new ByteArrayInputStream(strXML.getBytes(StandardCharsets.UTF_8)); |
|||
org.w3c.dom.Document doc = documentBuilder.parse(stream); |
|||
doc.getDocumentElement().normalize(); |
|||
NodeList nodeList = doc.getDocumentElement().getChildNodes(); |
|||
for (int idx=0; idx<nodeList.getLength(); ++idx) { |
|||
Node node = nodeList.item(idx); |
|||
if (node.getNodeType() == Node.ELEMENT_NODE) { |
|||
org.w3c.dom.Element element = (org.w3c.dom.Element) node; |
|||
data.put(element.getNodeName(), element.getTextContent()); |
|||
} |
|||
} |
|||
try { |
|||
stream.close(); |
|||
} |
|||
catch (Exception ex) { |
|||
|
|||
} |
|||
return data; |
|||
} |
|||
|
|||
} |
|||
Loading…
Reference in new issue