C:\zhuyrWork\外卖项目代码\meal\src\main\java\com\mayocase\meal\service\utls\MessageUtil.java
package com.mayocase.meal.service.utls;
import com.mayocase.meal.service.dto.TextMessageDTO;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.io.Writer;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by wrlai on 2020/2/19
*/
public class MessageUtil {
/**
* 返回消息类型:文本
*/
public static final String RESP_MESSAGE_TYPE_TEXT = "text";
/**
* 返回消息类型:音乐
*/
public static final String RESP_MESSAGE_TYPE_MUSIC = "music";
/**
* 返回消息类型:图文
*/
public static final String RESP_MESSAGE_TYPE_NEWS = "news";
/**
* 返回消息类型:图片
*/
public static final String RESP_MESSAGE_TYPE_Image = "image";
/**
* 返回消息类型:语音
*/
public static final String RESP_MESSAGE_TYPE_Voice = "voice";
/**
* 返回消息类型:视频
*/
public static final String RESP_MESSAGE_TYPE_Video = "video";
/**
* 请求消息类型:文本
*/
public static final String REQ_MESSAGE_TYPE_TEXT = "text";
/**
* 请求消息类型:图片
*/
public static final String REQ_MESSAGE_TYPE_IMAGE = "image";
/**
* 请求消息类型:链接
*/
public static final String REQ_MESSAGE_TYPE_LINK = "link";
/**
* 请求消息类型:地理位置
*/
public static final String REQ_MESSAGE_TYPE_LOCATION = "location";
/**
* 请求消息类型:音频
*/
public static final String REQ_MESSAGE_TYPE_VOICE = "voice";
/**
* 请求消息类型:视频
*/
public static final String REQ_MESSAGE_TYPE_VIDEO = "video";
/**
* 请求消息类型:推送
*/
public static final String REQ_MESSAGE_TYPE_EVENT = "event";
/**
* 事件类型:subscribe(订阅)
*/
public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
/**
* 事件类型:unsubscribe(取消订阅)
*/
public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
/**
* 事件类型:CLICK(自定义菜单点击事件)
*/
public static final String EVENT_TYPE_CLICK = "CLICK";
/**
* 事件类型:VIEW(自定义菜单 URl 视图)
*/
public static final String EVENT_TYPE_VIEW = "VIEW";
/**
* 事件类型:LOCATION(上报地理位置事件)
*/
public static final String EVENT_TYPE_LOCATION = "LOCATION";
/**
* 事件类型:LOCATION(上报地理位置事件)
*/
@SuppressWarnings("unchecked")
public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
// 将解析结果存储在HashMap中
Map<String, String> map = new HashMap<String, String>();
// 从request中取得输入流
InputStream inputStream = request.getInputStream();
// 读取输入流
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
// 得到xml根元素
Element root = document.getRootElement();
// 得到根元素的所有子节点
List<Element> elementList = root.elements();
// 遍历所有子节点
for (Element e : elementList)
map.put(e.getName(), e.getText());
// 释放资源
inputStream.close();
inputStream = null;
return map;
}
@SuppressWarnings("unused")
private static XStream xstream = new XStream(new XppDriver() {
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 对所有xml节点的转换都增加CDATA标记
boolean cdata = true;
@SuppressWarnings("rawtypes")
public void startNode(String name, Class clazz) {
super.startNode(name, clazz);
}
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
} else {
writer.write(text);
}
}
};
}
});
/**
* 将文本消息对象转成XML
* @param textMessage
* @return
*/
public static String textMessageToXml(TextMessageDTO textMessage){
XStream xstream = new XStream();
//将xml的根节点替换成<xml> 默认为TextMessage的包名
xstream.alias("xml", textMessage.getClass());
return xstream.toXML(textMessage);
}
/**
* 拼接关注主菜单
*/
public static String subscribeText(String mphost, String merchantName){
StringBuffer sb = new StringBuffer();
sb.append("亲爱的,你终于来啦!感谢关注"+merchantName+"服务号。\n\n");
sb.append("点击菜单里的<a href=\"" + mphost + "\">「开始点餐」</a> 即可微信下单订餐!");
return sb.toString();
}
public static String menuAllText(){
StringBuffer sb = new StringBuffer();
sb.append("如有意见或建议请留言,或者拨打门店电话!谢谢!");
return sb.toString();
}
/**
* 初始化回复消息
*/
public static String initText(String toUSerName,String fromUserName,String content){
TextMessageDTO text = new TextMessageDTO();
text.setFromUserName(toUSerName);
text.setToUserName(fromUserName);
text.setMsgType(REQ_MESSAGE_TYPE_TEXT);
text.setCreateTime(new Date().getTime()+"");
text.setContent(content);
return MessageUtil.textMessageToXml(text);
}
}
C:\zhuyrWork\外卖项目代码\meal\src\main\java\com\mayocase\meal\web\rest\WxWebViewAuthResource.java
package com.mayocase.meal.web.rest;
import com.mayocase.meal.client.WxException;
import com.mayocase.meal.client.WxMPClient;
import com.mayocase.meal.client.WxQrcodeClient;
import com.mayocase.meal.config.ApplicationProperties;
import com.mayocase.meal.service.MemberService;
import com.mayocase.meal.service.RewardPointsService;
import com.mayocase.meal.service.dto.MemberDTO;
import com.mayocase.meal.service.dto.RewardPointsDTO;
import com.mayocase.meal.service.dto.ShopDTO;
import com.mayocase.meal.service.utls.MessageUtil;
import com.mayocase.meal.service.utls.WeChatCheckUtil;
import liquibase.util.StringUtils;
import org.apache.http.client.ClientProtocolException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.*;
@Controller
@RequestMapping("/weixin")
public class WxWebViewAuthResource {
// 微信网页授权路径
private final String WX_WEBVIEW_AUTH_PATH = "/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect";
@Autowired
private ApplicationProperties applicationProperties;
@Autowired
private WxMPClient wxMPClient;
@Autowired
private MemberService memberService;
@Autowired
private RewardPointsService rewardPointsService;
@Value("${weixin.open.host}")
private String wxOpenHost;
@Value("${weixin.mp.appID}")
private String wxMPAppID;
@Value("${application.mp-url}")
private String wxUrl;
@Value("${application.merchant-name}")
private String merchantName;
@Autowired
private WxQrcodeClient wxQrcodeClient;
private static final Logger logger = LoggerFactory.getLogger(WxWebViewAuthResource.class);
@GetMapping("/authorize")
public String authorize(@RequestParam("return_url") String returnURL) throws UnsupportedEncodingException {
logger.info("微信开始过授权-------------------------------");
logger.info("回调地址-------------------------------" + returnURL);
String redirectURI = applicationProperties.getHost() + "/weixin/userinfo";
String encodedRedirectURI = URLEncoder.encode(redirectURI, "utf-8");
String state = URLEncoder.encode(returnURL, "utf-8");
String authURL = String.format(wxOpenHost + WX_WEBVIEW_AUTH_PATH, wxMPAppID, encodedRedirectURI, "snsapi_userinfo",
state);
logger.info("跳转地址:" + authURL);
return "redirect:" + authURL;
}
@GetMapping("/userinfo")
public String userinfo(@RequestParam("code") String code, @RequestParam("state") String returnURL)
throws ClientProtocolException, IOException, WxException {
MemberDTO member = wxMPClient.getUserInfoWithWebViewAuth(code);
logger.info("回调地址-------------------------------userinfo");
// 注册或更新会员信息
MemberDTO persistedMember = memberService.findByOpenId(member.getOpenId());
if (persistedMember != null) {
member.setId(persistedMember.getId());
member.setSn(persistedMember.getSn());
member.setCreatedDate(persistedMember.getCreatedDate());
member.setLastLoginDate(persistedMember.getLastLoginDate());
}else{
member.setLastLoginDate(Instant.now());
}
try {
member = memberService.save(member);
}catch (Exception e){
member.setSn(getDate());
memberService.save(member);
}
logger.info("登入的会员信息:{}", member.toString());
// 检查积分帐户是否存在,不存在则创建
boolean existed = rewardPointsService.isExistByMember(persistedMember);
if (!existed) {
RewardPointsDTO rewardPointsDTO = new RewardPointsDTO();
rewardPointsDTO.setBalance(0);
rewardPointsDTO.setMemberId(member.getId());
rewardPointsDTO.setMemberSn(member.getSn());
rewardPointsDTO.setLastStatisticalDate(Instant.now());
rewardPointsService.save(rewardPointsDTO);
}
return "redirect:" + returnURL + "?openId=" + member.getOpenId();
}
@RequestMapping("/getJsApi")
@ResponseBody
public Map<String,Object> getJsApi(String wxMphost) throws Exception {
Map map = new HashMap();
String token = wxMPClient.getAccessToken();
String ticket = wxMPClient.getJsapiTicket(token);
//3、时间戳和随机字符串
String noncestr = UUID.randomUUID().toString().replace("-", "").substring(0, 16);//随机字符串
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);//时间戳
StringBuilder string1Builder = new StringBuilder();
string1Builder.append("jsapi_ticket=").append(ticket).append("&")
.append("noncestr=").append(noncestr).append("&")
.append("timestamp=").append(timestamp).append("&")
.append("url=").append(wxMphost);
logger.info("jsApi签名参数: " + string1Builder.toString());
String sign = SHA1(string1Builder.toString());
map.put("sign", sign);
map.put("timestamp", timestamp);
map.put("noncestr", noncestr);
map.put("appid", wxMPAppID);
return map;
}
/* @RequestMapping("/getTicket")
@ResponseBody
public String getTicket() throws Exception {
String token = wxMPClient.getAccessToken();
String ticket = wxMPClient.getJsapiTicket(token);
return ticket;
}
@RequestMapping("/getToken")
@ResponseBody
public String getToken() throws Exception {
Map map = new HashMap();
String token = wxMPClient.getAccessToken();
return token;
}*/
@RequestMapping("/getQrcode")
@ResponseBody
public String getQrcode() throws Exception{
String result = wxQrcodeClient.getQrcode1(new Random(99999).nextInt());
return result;
}
/**
* 微信核心接入
*
* @param signature 签名
* @param timestamp 时间戳
* @param nonce 随机数
* @param echostr 随机字符串
* @return 接入成功:enchostr,接入失败:""
*/
@GetMapping
@ResponseBody
public String wxCoreAccess(@RequestParam(name = "signature", required = false) String signature,
@RequestParam(name = "timestamp", required = false) String timestamp,
@RequestParam(name = "nonce", required = false) String nonce,
@RequestParam(name = "echostr", required = false) String echostr) {
logger.info("【微信核心接入】,请求接入参数,signature={},timestamp={},nonce={},echostr={}", signature, timestamp, nonce, echostr);
if (WeChatCheckUtil.checkSignature(signature, timestamp, nonce)) {
logger.info("【微信核心接入】,参数校验正确,接入成功!");
return echostr;
} else {
logger.info("【微信核心接入,参数校验错误,接入失败!】");
return "";
}
}
// post方法用于接收微信服务端消息
@RequestMapping
public void DoPost(HttpServletRequest request, HttpServletResponse response) throws Exception {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
logger.info("接收微信推送消息---------------------------------------------------");
String message = "";
try {
Map<String, String> map = MessageUtil.parseXml(request);
//添加门店
logger.info("微信推送事件类型:" + map.get("Event"));
logger.info("微信推送事件类型:" + map.get("MsgType"));
logger.info("参数 " + map.toString());
String mesType = map.get("MsgType");
String event = map.get("Event");
if(!StringUtils.isEmpty(mesType)){
if("text".equals(mesType) || "voice".equals(mesType) || "image".equals(mesType) || "video".equals(mesType)
|| "link".equals(mesType) || "location".equals(mesType) || "shortvideo".equals(mesType)){
logger.info("文本");
String ToUserName = map.get("ToUserName");
String FromUserName = map.get("FromUserName");
message = MessageUtil.initText(ToUserName, FromUserName, MessageUtil.menuAllText());
logger.info("返回message: " + message.toString());
response.getWriter().write(message);
}else if(event.equals("subscribe")){
String ToUserName = map.get("ToUserName");
String FromUserName = map.get("FromUserName");
message = MessageUtil.initText(ToUserName, FromUserName, MessageUtil.subscribeText(wxUrl,merchantName));
logger.info("返回message: " + message.toString());
response.getWriter().write(message);
}
}
} catch (Exception e) {
logger.error(e.getMessage());
}
}
public static String SHA1(String decript) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(decript.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
// 字节数组转换为 十六进制 数
for (int i = 0; i < messageDigest.length; i++) {
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
if (shaHex.length() < 2) {
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
public String getDate(){
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");
String date = sdf.format(new Date());
date= date.replace("-","");
date= date.replace(" ","");
date=date.replace(":","");
date=date.replace(".","");
return date;
}
}
C:\zhuyrWork\外卖项目代码\meal\src\main\java\com\mayocase\meal\config\ApplicationProperties.java
package com.mayocase.meal.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Properties specific to Meal.
* <p>
* Properties are configured in the {@code application.yml} file.
* See {@link io.github.jhipster.config.JHipsterProperties} for a good example.
*/
@ConfigurationProperties(prefix = "application", ignoreUnknownFields = false)
public class ApplicationProperties {
private String host;
private String mpUrl;
private String merchantName;
/**
* @return the host
*/
public String getHost() {
return host;
}
/**
* @param host the host to set
*/
public void setHost(String host) {
this.host = host;
}
/**
* @return String return the mpUrl
*/
public String getMpUrl() {
return mpUrl;
}
/**
* @param mpUrl the mpUrl to set
*/
public void setMpUrl(String mpUrl) {
this.mpUrl = mpUrl;
}
public String getMerchantName() {
return merchantName;
}
public void setMerchantName(String merchantName) {
this.merchantName = merchantName;
}
}
C:\zhuyrWork\外卖项目代码\meal\src\main\resources\config\application.yml
# ===================================================================
# Application specific properties
# Add your own application properties here, see the ApplicationProperties class
# to have type-safe configuration, like in the JHipsterProperties above
#
# More documentation is available at:
# https://www.jhipster.tech/common-application-properties/
# ===================================================================
application:
host: 'http://localhost'
mp-url: 'http://localhost/#/'
merchant-name: '商户名称'
C:\zhuyrWork\外卖项目代码\meal\src\main\java\com\mayocase\meal\service\PayService.java
package com.mayocase.meal.service;
import com.lly835.bestpay.constants.WxPayConstants;
import com.lly835.bestpay.model.PayResponse;
import com.lly835.bestpay.model.RefundResponse;
import com.lly835.bestpay.model.wxpay.WxPayApi;
import com.lly835.bestpay.model.wxpay.response.WxPayAsyncResponse;
import com.lly835.bestpay.model.wxpay.response.WxPayRefundResponse;
import com.lly835.bestpay.model.wxpay.response.WxPaySyncResponse;
import com.lly835.bestpay.service.impl.WxPaySignature;
import com.lly835.bestpay.utils.JsonUtil;
import com.lly835.bestpay.utils.MoneyUtil;
import com.lly835.bestpay.utils.RandomUtil;
import com.lly835.bestpay.utils.XmlUtil;
import com.mayocase.meal.client.PrintClient;
import com.mayocase.meal.config.MyX509TrustManager;
import com.mayocase.meal.domain.SalesOrder;
import com.mayocase.meal.domain.SalesOrderMealItem;
import com.mayocase.meal.domain.SetMealDetails;
import com.mayocase.meal.domain.enumeration.OrderStatus;
import com.mayocase.meal.domain.enumeration.OrderType;
import com.mayocase.meal.service.dto.MealDTO;
import com.mayocase.meal.service.dto.MealInventoryDTO;
import com.mayocase.meal.service.dto.MerchantDTO;
import com.mayocase.meal.service.dto.SalesOrderDTO;
import com.mayocase.meal.service.utls.WXPayUtil;
import com.mayocase.meal.web.rest.errors.ResultEnum;
import com.mayocase.meal.web.rest.errors.SellerException;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.http.ssl.SSLContexts;
import org.redisson.RedissonMultiLock;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.cloudfoundry.com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.simplexml.SimpleXmlConverterFactory;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.security.KeyStore;
import java.time.*;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Service
@Transactional
public class PayService {
private final Logger log = LoggerFactory.getLogger(PayService.class);
private static final String ORDER_NAME = "营养餐";
@Value("${weixin.mp.appID}")
private String wxMPAppID;
@Value("${weixin.mp.mchId}")
private String wxMchId;
@Value("${weixin.mp.notifyUrl}")
private String notifyUrl;
@Value("${weixin.mp.mchKey}")
private String mchKey;
@Value("${weixin.mp.keyPath}")
private String keyPath;
@Autowired
private MealInventoryService mealInventoryService;
@Autowired
private SalesOrderService salesOrderService;
@Autowired
private PrintClient printClient;
@Autowired
private MerchantService merchantService;
@Autowired
private RedissonClient redissonClient;
public RefundResponse refund(SalesOrder order, Integer refundFee) {
Map<String, String> paraMap = new HashMap<String, String>();
paraMap.put("out_trade_no",order.getSn());
paraMap.put("out_refund_no",order.getSn());
paraMap.put("total_fee", order.getPaymentInfo().getTotalPaymentAmount().toString());
paraMap.put("refund_fee", refundFee.toString());
paraMap.put("appid",wxMPAppID);
paraMap.put("mch_id",wxMchId);
paraMap.put("nonce_str", RandomUtil.getRandomStr());
try {
String sign = WXPayUtil.generateSignature(paraMap, mchKey);
paraMap.put("sign", sign);
//初始化证书
this.initSSLContext();
MyX509TrustManager myX509TrustManager = new MyX509TrustManager();
OkHttpClient clinet = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(), myX509TrustManager)
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(WxPayConstants.WXPAY_GATEWAY)
.addConverterFactory(SimpleXmlConverterFactory.create())
.client(clinet)
.build();
String xml = WXPayUtil.mapToXml(paraMap);//将所有参数(map)转xml格式
RequestBody body = RequestBody.create(MediaType.parse("application/xml; charset=utf-8"), xml);
Call<WxPayRefundResponse> call = retrofit.create(WxPayApi.class).refund(body);
Response<WxPayRefundResponse> retrofitResponse = null;
try {
retrofitResponse = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
if (!retrofitResponse.isSuccessful()) {
log.error("【微信退款】发起退款, 网络异常,订单编号:" + order.getSn());
}
WxPayRefundResponse response = retrofitResponse.body();
log.warn("退款结果:" + response.toString());
log.warn("退款结果:" + response);
if (!response.getReturnCode().equals(WxPayConstants.SUCCESS)) {
log.error("【微信退款】发起退款, returnCode != SUCCESS, returnMsg = " + response.getReturnMsg() + ", 订单编号:" + order.getSn());
throw new SellerException(ResultEnum.REFUND_ERROR);
}
if (!response.getResultCode().equals(WxPayConstants.SUCCESS)) {
log.error("【微信退款】发起退款, resultCode != SUCCESS, err_code = " + response.getErrCode() + " err_code_des=" + response.getErrCodeDes() +", 订单编号:" + order.getSn());
log.error("-----------" + response.getErrCodeDes().equals("订单已全额退款"));
//微信公众号人工退款
if(response.getErrCodeDes().equals("订单已全额退款")){
return null;
}else{
throw new SellerException(ResultEnum.REFUND_ERROR);
}
}
return this.buildRefundResponse(response);
}catch (Exception e){
e.printStackTrace();
throw new SellerException(ResultEnum.REFUND_ERROR);
}
}
private PayResponse buildPayResponse(WxPaySyncResponse response) {
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
String nonceStr = RandomUtil.getRandomStr();
String packAge = "prepay_id=" + response.getPrepayId();
String signType = "MD5";
//先构造要签名的map
Map<String, String> map = new HashMap<>();
map.put("appId", response.getAppid());
map.put("timeStamp", timeStamp);
map.put("nonceStr", nonceStr);
map.put("package", packAge);
map.put("signType", signType);
PayResponse payResponse = new PayResponse();
payResponse.setAppId(response.getAppid());
payResponse.setTimeStamp(timeStamp);
payResponse.setNonceStr(nonceStr);
payResponse.setPackAge(packAge);
payResponse.setSignType(signType);
payResponse.setPaySign(WxPaySignature.sign(map, mchKey));
return payResponse;
}
public PayResponse asyncNotify(String notifyData) {
//xml解析为对象
WxPayAsyncResponse asyncResponse = (WxPayAsyncResponse) XmlUtil.fromXML(notifyData, WxPayAsyncResponse.class);
//签名校验
if (!WxPaySignature.verify(buildMap(asyncResponse), mchKey)) {
log.error("【微信支付异步通知】签名验证失败, response={}", asyncResponse);
throw new RuntimeException("【微信支付异步通知】签名验证失败");
}
if(!asyncResponse.getReturnCode().equals(WxPayConstants.SUCCESS)) {
throw new RuntimeException("【微信支付异步通知】发起支付, returnCode != SUCCESS, returnMsg = " + asyncResponse.getReturnMsg());
}
//该订单已支付直接返回
if (!asyncResponse.getResultCode().equals(WxPayConstants.SUCCESS)
&& asyncResponse.getErrCode().equals("ORDERPAID")) {
return buildPayResponse(asyncResponse);
}
if (!asyncResponse.getResultCode().equals(WxPayConstants.SUCCESS)) {
throw new RuntimeException("【微信支付异步通知】发起支付, resultCode != SUCCESS, err_code = " + asyncResponse.getErrCode() + " err_code_des=" + asyncResponse.getErrCodeDes());
}
return buildPayResponse(asyncResponse);
}
public PayResponse create(SalesOrderDTO orderDTO, String openId) {
//拼接统一下单地址参数
Map<String, String> paraMap = new HashMap<String, String>();
//获取请求ip地址
paraMap.put("appid", wxMPAppID);
paraMap.put("body", ORDER_NAME);
paraMap.put("mch_id", wxMchId);
paraMap.put("nonce_str", WXPayUtil.generateNonceStr());
paraMap.put("openid", openId);
paraMap.put("out_trade_no", orderDTO.getSn());//订单号
paraMap.put("spbill_create_ip", "8.8.8.8");
paraMap.put("total_fee", orderDTO.getPaymentInfo().getTotalPaymentAmount().toString());
paraMap.put("trade_type", "JSAPI");
paraMap.put("notify_url",notifyUrl);// 此路径是微信服务器调用支付结果通知路径随意写
try{
String sign = WXPayUtil.generateSignature(paraMap, mchKey);
paraMap.put("sign", sign);
String xml = WXPayUtil.mapToXml(paraMap);//将所有参数(map)转xml格式
log.info("请求参数{}" + xml);
RequestBody body = RequestBody.create(MediaType.parse("application/xml; charset=utf-8"),xml);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(WxPayConstants.WXPAY_GATEWAY)
.addConverterFactory(SimpleXmlConverterFactory.create())
.build();
Call<WxPaySyncResponse> call = retrofit.create(WxPayApi.class).unifiedorder(body);
Response<WxPaySyncResponse> retrofitResponse = null;
try{
retrofitResponse = call.execute();
}catch (IOException e) {
e.printStackTrace();
}
if (!retrofitResponse.isSuccessful()) {
throw new RuntimeException("【微信统一支付】发起支付, 网络异常");
}
WxPaySyncResponse response = retrofitResponse.body();
log.info("【微信统一支付】response={}", JsonUtil.toJson(response));
if(!response.getReturnCode().equals(WxPayConstants.SUCCESS)) {
throw new RuntimeException("【微信统一支付】发起支付, returnCode != SUCCESS, returnMsg = " + response.getReturnMsg());
}
if (!response.getResultCode().equals(WxPayConstants.SUCCESS)) {
throw new RuntimeException("【微信统一支付】发起支付, resultCode != SUCCESS, err_code = " + response.getErrCode() + " err_code_des=" + response.getErrCodeDes());
}
return this.buildPayResponse(response);
}catch (Exception e){
e.printStackTrace();
throw new SellerException(ResultEnum.PARAM_ERROR.getCode(), "sign失败");
}
}
public void zeroNotify(String sn) throws Exception{
log.info("支付为0开始");
Optional<SalesOrderDTO> op = salesOrderService.getBySn(sn);
//判断订单是否存在
if (!op.isPresent()) {
log.info("【微信支付】异步通知, 订单不存在, orderId={}"+ sn);
throw new SellerException(ResultEnum.ORDER_NOT_EXIST);
}
SalesOrderDTO orderDTO = op.get();
if(orderDTO.getStatus().equals(OrderStatus.PAID)){
return;
}
//修改订单的支付状态和修改实际数量
try {
salesOrderService.paid(orderDTO);
}catch (ObjectOptimisticLockingFailureException s){
op = salesOrderService.getBySn(sn);
salesOrderService.paid(op.get());
}
LocalDateTime create = LocalDateTime.ofInstant(orderDTO.getDiningDate(), ZoneOffset.UTC);
LocalDate localDate = create.toLocalDate();
LocalDateTime localDateTimeNow = LocalDateTime.of(localDate.getYear(), localDate.getMonth(), localDate.getDayOfMonth(),0,0,0);
localDateTimeNow = localDateTimeNow.minusHours(8);
Instant from = localDateTimeNow.toInstant(ZoneOffset.UTC);
log.info("查询reids时间段:" + from);
//当天的去除库存占用,添加到销量
boolean istoday = false;
LocalDate localDate1 = LocalDate.now();
LocalDateTime localDateTime = LocalDateTime.of(localDate1.getYear(), localDate1.getMonth(), localDate1.getDayOfMonth(),0,0,0);
localDateTime = localDateTime.minusHours(8);
Instant todayFrom = localDateTime.toInstant(ZoneOffset.UTC);
Instant todayTo = localDateTime.minusDays(-1).toInstant(ZoneOffset.UTC);
if(orderDTO.getDiningDate().compareTo(todayFrom)>0 && orderDTO.getDiningDate().compareTo(todayTo) <0){
istoday = true;
}
log.info("是否是今天:" + istoday);
RLock rLockStock = redissonClient.getLock("create");
boolean lock = rLockStock.tryLock(10,10,TimeUnit.SECONDS);
if(lock){
Iterator<SalesOrderMealItem> it = orderDTO.getMealItems().iterator();
while (it.hasNext()){
SalesOrderMealItem item = it.next();
if(item.isSetMeal()){
Iterator<SetMealDetails> detailsIterator = item.getSetMealDetails().iterator();
while (detailsIterator.hasNext()){
SetMealDetails details = detailsIterator.next();
RBucket<Map<String,Object>> stock = redissonClient.getBucket(from+orderDTO.getShopSn()+details.getMealPartSn());
Map<String,Object> m = stock.get();
m.put("soldQuantity", (Integer.valueOf(m.get("soldQuantity").toString()) + details.getQuantity()));
if(istoday) {
log.info("当天的去除占用--------------------------------");
m.put("heldQuantity", (Integer.valueOf(m.get("heldQuantity").toString()) - details.getQuantity()));
}
stock.set(m);
}
}else{
RBucket<Map<String,Object>> stock = redissonClient.getBucket(from+orderDTO.getShopSn()+item.getMealSn());
Map<String,Object> m = stock.get();
m.put("soldQuantity", (Integer.valueOf(m.get("soldQuantity").toString()) + item.getQuantity()));
if(istoday) {
log.info("当天的去除占用--------------------------------");
m.put("heldQuantity", (Integer.valueOf(m.get("heldQuantity").toString()) - item.getQuantity()));
}
stock.set(m);
}
}
}
List<MerchantDTO> list = merchantService.findAll();
try {
printClient.print(op.get(), (list == null || list.size() == 0) ? new MerchantDTO() : list.get(0),3);
}catch (Exception e){
e.printStackTrace();
}
log.info("微信回调流程走完");
}
@Transactional
public PayResponse notify(String notifyData) throws Exception{
log.info("微信支付回调{}" + notifyData);
PayResponse payResponse = this.asyncNotify(notifyData);
log.info("【微信支付】异步通知, payResponse={}"+ JsonUtil.toJson(payResponse));
RLock rLock = redissonClient.getLock(payResponse.getOrderId());
try {
rLock.lock();
//查询订单
Optional<SalesOrderDTO> op = salesOrderService.getBySn(payResponse.getOrderId());
//判断订单是否存在
if (!op.isPresent()) {
log.info("【微信支付】异步通知, 订单不存在, orderId={}"+ payResponse.getOrderId());
throw new SellerException(ResultEnum.ORDER_NOT_EXIST);
}
SalesOrderDTO orderDTO = op.get();
if(orderDTO.getStatus().equals(OrderStatus.PAID)){
return payResponse;
}
BigDecimal num1 = new BigDecimal("100");
BigDecimal bigDecimal = new BigDecimal(payResponse.getOrderAmount().toString());
bigDecimal = bigDecimal.multiply(num1);
//判断金额是否一致(0.10 0.1)
BigDecimal b = new BigDecimal(orderDTO.getPaymentInfo().getTotalPaymentAmount());
if (bigDecimal.compareTo(b) != 0) {
log.info("【微信支付】异步通知, 订单金额不一致, orderId={}, 微信通知金额={}, 系统金额={}"+
payResponse.getOrderId()+","+
payResponse.getOrderAmount()+","+
orderDTO.getPaymentInfo().getTotalPaymentAmount());
throw new SellerException(ResultEnum.WXPAY_NOTIFY_MONEY_VERIFY_ERROR);
}
LocalDateTime create = LocalDateTime.ofInstant(orderDTO.getDiningDate(), ZoneOffset.UTC);
LocalDate localDate = create.toLocalDate();
LocalDateTime localDateTimeNow = LocalDateTime.of(localDate.getYear(), localDate.getMonth(), localDate.getDayOfMonth(),0,0,0);
localDateTimeNow = localDateTimeNow.minusHours(8);
Instant from = localDateTimeNow.toInstant(ZoneOffset.UTC);
log.info("查询reids时间段:" + from);
//当天的去除库存占用,添加到销量
boolean istoday = false;
LocalDate localDate1 = LocalDate.now();
LocalDateTime localDateTime = LocalDateTime.of(localDate1.getYear(), localDate1.getMonth(), localDate1.getDayOfMonth(),0,0,0);
localDateTime = localDateTime.minusHours(8);
Instant todayFrom = localDateTime.toInstant(ZoneOffset.UTC);
Instant todayTo = localDateTime.minusDays(-1).toInstant(ZoneOffset.UTC);
if(orderDTO.getDiningDate().compareTo(todayFrom)>0 && orderDTO.getDiningDate().compareTo(todayTo) <0){
istoday = true;
}
log.info("是否是今天:" + istoday);
RLock rLockStock = redissonClient.getLock("create");
boolean lock = rLockStock.tryLock(10,10,TimeUnit.SECONDS);
if(lock){
Iterator<SalesOrderMealItem> it = orderDTO.getMealItems().iterator();
while (it.hasNext()){
SalesOrderMealItem item = it.next();
if(item.isSetMeal()){
Iterator<SetMealDetails> detailsIterator = item.getSetMealDetails().iterator();
while (detailsIterator.hasNext()){
SetMealDetails details = detailsIterator.next();
RBucket<Map<String,Object>> stock = redissonClient.getBucket(from+orderDTO.getShopSn()+details.getMealPartSn());
Map<String,Object> m = stock.get();
m.put("soldQuantity", (Integer.valueOf(m.get("soldQuantity").toString()) + details.getQuantity()*item.getQuantity()));
if(istoday) {
log.info("当天的去除占用--------------------------------");
m.put("heldQuantity", (Integer.valueOf(m.get("heldQuantity").toString()) - details.getQuantity()*item.getQuantity()));
}else{
log.info("预售的添加预售数量--------------------------------");
m.put("reservedQuantity", (Integer.valueOf(m.get("reservedQuantity").toString()) + details.getQuantity()*item.getQuantity()));
}
stock.set(m);
}
}else{
RBucket<Map<String,Object>> stock = redissonClient.getBucket(from+orderDTO.getShopSn()+item.getMealSn());
Map<String,Object> m = stock.get();
m.put("soldQuantity", (Integer.valueOf(m.get("soldQuantity").toString()) + item.getQuantity()));
if(istoday) {
log.info("当天的去除占用--------------------------------");
m.put("heldQuantity", (Integer.valueOf(m.get("heldQuantity").toString()) - item.getQuantity()));
}else{
log.info("预售的添加预售数量--------------------------------");
m.put("reservedQuantity", (Integer.valueOf(m.get("reservedQuantity").toString()) + item.getQuantity()));
}
stock.set(m);
}
}
}
//打印小票
SalesOrder salesOrder = salesOrderService.paid(orderDTO);
List<MerchantDTO> list = merchantService.findAll();
try {
printClient.print(salesOrder, (list == null || list.size() == 0) ? new MerchantDTO() : list.get(0),3);
}catch (Exception e){
e.printStackTrace();
}
log.info("微信回调流程走完");
return payResponse;
}catch (Exception e){
e.printStackTrace();
throw new Exception();
}finally {
rLock.unlock();
}
}
private Map<String, String> buildMap(WxPayAsyncResponse response) {
Map<String, String> map = new HashMap<>();
map.put("return_code", response.getReturnCode());
map.put("return_msg", response.getReturnMsg());
map.put("appid", response.getAppid());
map.put("mch_id", response.getMchId());
map.put("device_info", response.getDeviceInfo());
map.put("nonce_str", response.getNonceStr());
map.put("sign", response.getSign());
map.put("result_code", response.getResultCode());
map.put("err_code", response.getErrCode());
map.put("err_code_des", response.getErrCodeDes());
map.put("openid", response.getOpenid());
map.put("is_subscribe", response.getIsSubscribe());
map.put("trade_type", response.getTradeType());
map.put("bank_type", response.getBankType());
map.put("total_fee", String.valueOf(response.getTotalFee()));
map.put("fee_type", response.getFeeType());
map.put("cash_fee", response.getCashFee());
map.put("cash_fee_type", response.getCashFeeType());
map.put("coupon_fee", response.getCouponFee());
map.put("coupon_count", response.getCouponCount());
map.put("transaction_id", response.getTransactionId());
map.put("out_trade_no", response.getOutTradeNo());
map.put("attach", response.getAttach());
map.put("time_end", response.getTimeEnd());
return map;
}
private PayResponse buildPayResponse(WxPayAsyncResponse response) {
PayResponse payResponse = new PayResponse();
payResponse.setOrderAmount(MoneyUtil.Fen2Yuan(response.getTotalFee()));
payResponse.setOrderId(response.getOutTradeNo());
payResponse.setOutTradeNo(response.getTransactionId());
return payResponse;
}
/**
* 初始化证书
* @return
*/
private SSLContext initSSLContext() {
FileInputStream inputStream = null;
try {
String jarPath = this.getClass().getClassLoader().getResource(keyPath).getPath();
log.info("文件路径------------------------" + jarPath);
inputStream = new FileInputStream(new File(jarPath));
} catch (IOException e) {
throw new RuntimeException("读取微信商户证书文件出错", e);
}
try {
KeyStore keystore = KeyStore.getInstance("PKCS12");
char[] partnerId2charArray = wxMchId.toCharArray();
keystore.load(inputStream, partnerId2charArray);
this.sslContext = SSLContexts.custom().loadKeyMaterial(keystore, partnerId2charArray).build();
return this.sslContext;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("证书文件有问题,请核实!", e);
} finally {
IOUtils.closeQuietly(inputStream);
}
}
/**
* 证书内容
*/
private SSLContext sslContext;
private RefundResponse buildRefundResponse(WxPayRefundResponse response) {
RefundResponse refundResponse = new RefundResponse();
refundResponse.setOrderId(response.getOutTradeNo());
refundResponse.setOrderAmount(MoneyUtil.Fen2Yuan(response.getTotalFee()));
refundResponse.setOutTradeNo(response.getTransactionId());
refundResponse.setRefundId(response.getOutRefundNo());
refundResponse.setOutRefundNo(response.getRefundId());
return refundResponse;
}
}