image.png
    C:\zhuyrWork\外卖项目代码\meal\src\main\java\com\mayocase\meal\service\utls\MessageUtil.java

    1. package com.mayocase.meal.service.utls;
    2. import com.mayocase.meal.service.dto.TextMessageDTO;
    3. import com.thoughtworks.xstream.XStream;
    4. import com.thoughtworks.xstream.core.util.QuickWriter;
    5. import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
    6. import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
    7. import com.thoughtworks.xstream.io.xml.XppDriver;
    8. import org.dom4j.Document;
    9. import org.dom4j.Element;
    10. import org.dom4j.io.SAXReader;
    11. import javax.servlet.http.HttpServletRequest;
    12. import java.io.InputStream;
    13. import java.io.Writer;
    14. import java.util.Date;
    15. import java.util.HashMap;
    16. import java.util.List;
    17. import java.util.Map;
    18. /**
    19. * Created by wrlai on 2020/2/19
    20. */
    21. public class MessageUtil {
    22. /**
    23. * 返回消息类型:文本
    24. */
    25. public static final String RESP_MESSAGE_TYPE_TEXT = "text";
    26. /**
    27. * 返回消息类型:音乐
    28. */
    29. public static final String RESP_MESSAGE_TYPE_MUSIC = "music";
    30. /**
    31. * 返回消息类型:图文
    32. */
    33. public static final String RESP_MESSAGE_TYPE_NEWS = "news";
    34. /**
    35. * 返回消息类型:图片
    36. */
    37. public static final String RESP_MESSAGE_TYPE_Image = "image";
    38. /**
    39. * 返回消息类型:语音
    40. */
    41. public static final String RESP_MESSAGE_TYPE_Voice = "voice";
    42. /**
    43. * 返回消息类型:视频
    44. */
    45. public static final String RESP_MESSAGE_TYPE_Video = "video";
    46. /**
    47. * 请求消息类型:文本
    48. */
    49. public static final String REQ_MESSAGE_TYPE_TEXT = "text";
    50. /**
    51. * 请求消息类型:图片
    52. */
    53. public static final String REQ_MESSAGE_TYPE_IMAGE = "image";
    54. /**
    55. * 请求消息类型:链接
    56. */
    57. public static final String REQ_MESSAGE_TYPE_LINK = "link";
    58. /**
    59. * 请求消息类型:地理位置
    60. */
    61. public static final String REQ_MESSAGE_TYPE_LOCATION = "location";
    62. /**
    63. * 请求消息类型:音频
    64. */
    65. public static final String REQ_MESSAGE_TYPE_VOICE = "voice";
    66. /**
    67. * 请求消息类型:视频
    68. */
    69. public static final String REQ_MESSAGE_TYPE_VIDEO = "video";
    70. /**
    71. * 请求消息类型:推送
    72. */
    73. public static final String REQ_MESSAGE_TYPE_EVENT = "event";
    74. /**
    75. * 事件类型:subscribe(订阅)
    76. */
    77. public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
    78. /**
    79. * 事件类型:unsubscribe(取消订阅)
    80. */
    81. public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
    82. /**
    83. * 事件类型:CLICK(自定义菜单点击事件)
    84. */
    85. public static final String EVENT_TYPE_CLICK = "CLICK";
    86. /**
    87. * 事件类型:VIEW(自定义菜单 URl 视图)
    88. */
    89. public static final String EVENT_TYPE_VIEW = "VIEW";
    90. /**
    91. * 事件类型:LOCATION(上报地理位置事件)
    92. */
    93. public static final String EVENT_TYPE_LOCATION = "LOCATION";
    94. /**
    95. * 事件类型:LOCATION(上报地理位置事件)
    96. */
    97. @SuppressWarnings("unchecked")
    98. public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
    99. // 将解析结果存储在HashMap中
    100. Map<String, String> map = new HashMap<String, String>();
    101. // 从request中取得输入流
    102. InputStream inputStream = request.getInputStream();
    103. // 读取输入流
    104. SAXReader reader = new SAXReader();
    105. Document document = reader.read(inputStream);
    106. // 得到xml根元素
    107. Element root = document.getRootElement();
    108. // 得到根元素的所有子节点
    109. List<Element> elementList = root.elements();
    110. // 遍历所有子节点
    111. for (Element e : elementList)
    112. map.put(e.getName(), e.getText());
    113. // 释放资源
    114. inputStream.close();
    115. inputStream = null;
    116. return map;
    117. }
    118. @SuppressWarnings("unused")
    119. private static XStream xstream = new XStream(new XppDriver() {
    120. public HierarchicalStreamWriter createWriter(Writer out) {
    121. return new PrettyPrintWriter(out) {
    122. // 对所有xml节点的转换都增加CDATA标记
    123. boolean cdata = true;
    124. @SuppressWarnings("rawtypes")
    125. public void startNode(String name, Class clazz) {
    126. super.startNode(name, clazz);
    127. }
    128. protected void writeText(QuickWriter writer, String text) {
    129. if (cdata) {
    130. writer.write("<![CDATA[");
    131. writer.write(text);
    132. writer.write("]]>");
    133. } else {
    134. writer.write(text);
    135. }
    136. }
    137. };
    138. }
    139. });
    140. /**
    141. * 将文本消息对象转成XML
    142. * @param textMessage
    143. * @return
    144. */
    145. public static String textMessageToXml(TextMessageDTO textMessage){
    146. XStream xstream = new XStream();
    147. //将xml的根节点替换成<xml> 默认为TextMessage的包名
    148. xstream.alias("xml", textMessage.getClass());
    149. return xstream.toXML(textMessage);
    150. }
    151. /**
    152. * 拼接关注主菜单
    153. */
    154. public static String subscribeText(String mphost, String merchantName){
    155. StringBuffer sb = new StringBuffer();
    156. sb.append("亲爱的,你终于来啦!感谢关注"+merchantName+"服务号。\n\n");
    157. sb.append("点击菜单里的<a href=\"" + mphost + "\">「开始点餐」</a> 即可微信下单订餐!");
    158. return sb.toString();
    159. }
    160. public static String menuAllText(){
    161. StringBuffer sb = new StringBuffer();
    162. sb.append("如有意见或建议请留言,或者拨打门店电话!谢谢!");
    163. return sb.toString();
    164. }
    165. /**
    166. * 初始化回复消息
    167. */
    168. public static String initText(String toUSerName,String fromUserName,String content){
    169. TextMessageDTO text = new TextMessageDTO();
    170. text.setFromUserName(toUSerName);
    171. text.setToUserName(fromUserName);
    172. text.setMsgType(REQ_MESSAGE_TYPE_TEXT);
    173. text.setCreateTime(new Date().getTime()+"");
    174. text.setContent(content);
    175. return MessageUtil.textMessageToXml(text);
    176. }
    177. }

    image.png
    C:\zhuyrWork\外卖项目代码\meal\src\main\java\com\mayocase\meal\web\rest\WxWebViewAuthResource.java

    1. package com.mayocase.meal.web.rest;
    2. import com.mayocase.meal.client.WxException;
    3. import com.mayocase.meal.client.WxMPClient;
    4. import com.mayocase.meal.client.WxQrcodeClient;
    5. import com.mayocase.meal.config.ApplicationProperties;
    6. import com.mayocase.meal.service.MemberService;
    7. import com.mayocase.meal.service.RewardPointsService;
    8. import com.mayocase.meal.service.dto.MemberDTO;
    9. import com.mayocase.meal.service.dto.RewardPointsDTO;
    10. import com.mayocase.meal.service.dto.ShopDTO;
    11. import com.mayocase.meal.service.utls.MessageUtil;
    12. import com.mayocase.meal.service.utls.WeChatCheckUtil;
    13. import liquibase.util.StringUtils;
    14. import org.apache.http.client.ClientProtocolException;
    15. import org.slf4j.Logger;
    16. import org.slf4j.LoggerFactory;
    17. import org.springframework.beans.BeanUtils;
    18. import org.springframework.beans.factory.annotation.Autowired;
    19. import org.springframework.beans.factory.annotation.Value;
    20. import org.springframework.stereotype.Controller;
    21. import org.springframework.web.bind.annotation.GetMapping;
    22. import org.springframework.web.bind.annotation.RequestMapping;
    23. import org.springframework.web.bind.annotation.RequestParam;
    24. import org.springframework.web.bind.annotation.ResponseBody;
    25. import javax.servlet.http.HttpServletRequest;
    26. import javax.servlet.http.HttpServletResponse;
    27. import java.io.IOException;
    28. import java.io.UnsupportedEncodingException;
    29. import java.net.URLEncoder;
    30. import java.security.MessageDigest;
    31. import java.security.NoSuchAlgorithmException;
    32. import java.text.SimpleDateFormat;
    33. import java.time.Instant;
    34. import java.util.*;
    35. @Controller
    36. @RequestMapping("/weixin")
    37. public class WxWebViewAuthResource {
    38. // 微信网页授权路径
    39. private final String WX_WEBVIEW_AUTH_PATH = "/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect";
    40. @Autowired
    41. private ApplicationProperties applicationProperties;
    42. @Autowired
    43. private WxMPClient wxMPClient;
    44. @Autowired
    45. private MemberService memberService;
    46. @Autowired
    47. private RewardPointsService rewardPointsService;
    48. @Value("${weixin.open.host}")
    49. private String wxOpenHost;
    50. @Value("${weixin.mp.appID}")
    51. private String wxMPAppID;
    52. @Value("${application.mp-url}")
    53. private String wxUrl;
    54. @Value("${application.merchant-name}")
    55. private String merchantName;
    56. @Autowired
    57. private WxQrcodeClient wxQrcodeClient;
    58. private static final Logger logger = LoggerFactory.getLogger(WxWebViewAuthResource.class);
    59. @GetMapping("/authorize")
    60. public String authorize(@RequestParam("return_url") String returnURL) throws UnsupportedEncodingException {
    61. logger.info("微信开始过授权-------------------------------");
    62. logger.info("回调地址-------------------------------" + returnURL);
    63. String redirectURI = applicationProperties.getHost() + "/weixin/userinfo";
    64. String encodedRedirectURI = URLEncoder.encode(redirectURI, "utf-8");
    65. String state = URLEncoder.encode(returnURL, "utf-8");
    66. String authURL = String.format(wxOpenHost + WX_WEBVIEW_AUTH_PATH, wxMPAppID, encodedRedirectURI, "snsapi_userinfo",
    67. state);
    68. logger.info("跳转地址:" + authURL);
    69. return "redirect:" + authURL;
    70. }
    71. @GetMapping("/userinfo")
    72. public String userinfo(@RequestParam("code") String code, @RequestParam("state") String returnURL)
    73. throws ClientProtocolException, IOException, WxException {
    74. MemberDTO member = wxMPClient.getUserInfoWithWebViewAuth(code);
    75. logger.info("回调地址-------------------------------userinfo");
    76. // 注册或更新会员信息
    77. MemberDTO persistedMember = memberService.findByOpenId(member.getOpenId());
    78. if (persistedMember != null) {
    79. member.setId(persistedMember.getId());
    80. member.setSn(persistedMember.getSn());
    81. member.setCreatedDate(persistedMember.getCreatedDate());
    82. member.setLastLoginDate(persistedMember.getLastLoginDate());
    83. }else{
    84. member.setLastLoginDate(Instant.now());
    85. }
    86. try {
    87. member = memberService.save(member);
    88. }catch (Exception e){
    89. member.setSn(getDate());
    90. memberService.save(member);
    91. }
    92. logger.info("登入的会员信息:{}", member.toString());
    93. // 检查积分帐户是否存在,不存在则创建
    94. boolean existed = rewardPointsService.isExistByMember(persistedMember);
    95. if (!existed) {
    96. RewardPointsDTO rewardPointsDTO = new RewardPointsDTO();
    97. rewardPointsDTO.setBalance(0);
    98. rewardPointsDTO.setMemberId(member.getId());
    99. rewardPointsDTO.setMemberSn(member.getSn());
    100. rewardPointsDTO.setLastStatisticalDate(Instant.now());
    101. rewardPointsService.save(rewardPointsDTO);
    102. }
    103. return "redirect:" + returnURL + "?openId=" + member.getOpenId();
    104. }
    105. @RequestMapping("/getJsApi")
    106. @ResponseBody
    107. public Map<String,Object> getJsApi(String wxMphost) throws Exception {
    108. Map map = new HashMap();
    109. String token = wxMPClient.getAccessToken();
    110. String ticket = wxMPClient.getJsapiTicket(token);
    111. //3、时间戳和随机字符串
    112. String noncestr = UUID.randomUUID().toString().replace("-", "").substring(0, 16);//随机字符串
    113. String timestamp = String.valueOf(System.currentTimeMillis() / 1000);//时间戳
    114. StringBuilder string1Builder = new StringBuilder();
    115. string1Builder.append("jsapi_ticket=").append(ticket).append("&")
    116. .append("noncestr=").append(noncestr).append("&")
    117. .append("timestamp=").append(timestamp).append("&")
    118. .append("url=").append(wxMphost);
    119. logger.info("jsApi签名参数: " + string1Builder.toString());
    120. String sign = SHA1(string1Builder.toString());
    121. map.put("sign", sign);
    122. map.put("timestamp", timestamp);
    123. map.put("noncestr", noncestr);
    124. map.put("appid", wxMPAppID);
    125. return map;
    126. }
    127. /* @RequestMapping("/getTicket")
    128. @ResponseBody
    129. public String getTicket() throws Exception {
    130. String token = wxMPClient.getAccessToken();
    131. String ticket = wxMPClient.getJsapiTicket(token);
    132. return ticket;
    133. }
    134. @RequestMapping("/getToken")
    135. @ResponseBody
    136. public String getToken() throws Exception {
    137. Map map = new HashMap();
    138. String token = wxMPClient.getAccessToken();
    139. return token;
    140. }*/
    141. @RequestMapping("/getQrcode")
    142. @ResponseBody
    143. public String getQrcode() throws Exception{
    144. String result = wxQrcodeClient.getQrcode1(new Random(99999).nextInt());
    145. return result;
    146. }
    147. /**
    148. * 微信核心接入
    149. *
    150. * @param signature 签名
    151. * @param timestamp 时间戳
    152. * @param nonce 随机数
    153. * @param echostr 随机字符串
    154. * @return 接入成功:enchostr,接入失败:""
    155. */
    156. @GetMapping
    157. @ResponseBody
    158. public String wxCoreAccess(@RequestParam(name = "signature", required = false) String signature,
    159. @RequestParam(name = "timestamp", required = false) String timestamp,
    160. @RequestParam(name = "nonce", required = false) String nonce,
    161. @RequestParam(name = "echostr", required = false) String echostr) {
    162. logger.info("【微信核心接入】,请求接入参数,signature={},timestamp={},nonce={},echostr={}", signature, timestamp, nonce, echostr);
    163. if (WeChatCheckUtil.checkSignature(signature, timestamp, nonce)) {
    164. logger.info("【微信核心接入】,参数校验正确,接入成功!");
    165. return echostr;
    166. } else {
    167. logger.info("【微信核心接入,参数校验错误,接入失败!】");
    168. return "";
    169. }
    170. }
    171. // post方法用于接收微信服务端消息
    172. @RequestMapping
    173. public void DoPost(HttpServletRequest request, HttpServletResponse response) throws Exception {
    174. request.setCharacterEncoding("UTF-8");
    175. response.setCharacterEncoding("UTF-8");
    176. logger.info("接收微信推送消息---------------------------------------------------");
    177. String message = "";
    178. try {
    179. Map<String, String> map = MessageUtil.parseXml(request);
    180. //添加门店
    181. logger.info("微信推送事件类型:" + map.get("Event"));
    182. logger.info("微信推送事件类型:" + map.get("MsgType"));
    183. logger.info("参数 " + map.toString());
    184. String mesType = map.get("MsgType");
    185. String event = map.get("Event");
    186. if(!StringUtils.isEmpty(mesType)){
    187. if("text".equals(mesType) || "voice".equals(mesType) || "image".equals(mesType) || "video".equals(mesType)
    188. || "link".equals(mesType) || "location".equals(mesType) || "shortvideo".equals(mesType)){
    189. logger.info("文本");
    190. String ToUserName = map.get("ToUserName");
    191. String FromUserName = map.get("FromUserName");
    192. message = MessageUtil.initText(ToUserName, FromUserName, MessageUtil.menuAllText());
    193. logger.info("返回message: " + message.toString());
    194. response.getWriter().write(message);
    195. }else if(event.equals("subscribe")){
    196. String ToUserName = map.get("ToUserName");
    197. String FromUserName = map.get("FromUserName");
    198. message = MessageUtil.initText(ToUserName, FromUserName, MessageUtil.subscribeText(wxUrl,merchantName));
    199. logger.info("返回message: " + message.toString());
    200. response.getWriter().write(message);
    201. }
    202. }
    203. } catch (Exception e) {
    204. logger.error(e.getMessage());
    205. }
    206. }
    207. public static String SHA1(String decript) {
    208. try {
    209. MessageDigest digest = MessageDigest.getInstance("SHA-1");
    210. digest.update(decript.getBytes());
    211. byte messageDigest[] = digest.digest();
    212. // Create Hex String
    213. StringBuffer hexString = new StringBuffer();
    214. // 字节数组转换为 十六进制 数
    215. for (int i = 0; i < messageDigest.length; i++) {
    216. String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
    217. if (shaHex.length() < 2) {
    218. hexString.append(0);
    219. }
    220. hexString.append(shaHex);
    221. }
    222. return hexString.toString();
    223. } catch (NoSuchAlgorithmException e) {
    224. e.printStackTrace();
    225. }
    226. return "";
    227. }
    228. public String getDate(){
    229. SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");
    230. String date = sdf.format(new Date());
    231. date= date.replace("-","");
    232. date= date.replace(" ","");
    233. date=date.replace(":","");
    234. date=date.replace(".","");
    235. return date;
    236. }
    237. }

    image.png
    C:\zhuyrWork\外卖项目代码\meal\src\main\java\com\mayocase\meal\config\ApplicationProperties.java

    1. package com.mayocase.meal.config;
    2. import org.springframework.boot.context.properties.ConfigurationProperties;
    3. /**
    4. * Properties specific to Meal.
    5. * <p>
    6. * Properties are configured in the {@code application.yml} file.
    7. * See {@link io.github.jhipster.config.JHipsterProperties} for a good example.
    8. */
    9. @ConfigurationProperties(prefix = "application", ignoreUnknownFields = false)
    10. public class ApplicationProperties {
    11. private String host;
    12. private String mpUrl;
    13. private String merchantName;
    14. /**
    15. * @return the host
    16. */
    17. public String getHost() {
    18. return host;
    19. }
    20. /**
    21. * @param host the host to set
    22. */
    23. public void setHost(String host) {
    24. this.host = host;
    25. }
    26. /**
    27. * @return String return the mpUrl
    28. */
    29. public String getMpUrl() {
    30. return mpUrl;
    31. }
    32. /**
    33. * @param mpUrl the mpUrl to set
    34. */
    35. public void setMpUrl(String mpUrl) {
    36. this.mpUrl = mpUrl;
    37. }
    38. public String getMerchantName() {
    39. return merchantName;
    40. }
    41. public void setMerchantName(String merchantName) {
    42. this.merchantName = merchantName;
    43. }
    44. }

    image.png
    C:\zhuyrWork\外卖项目代码\meal\src\main\resources\config\application.yml

    1. # ===================================================================
    2. # Application specific properties
    3. # Add your own application properties here, see the ApplicationProperties class
    4. # to have type-safe configuration, like in the JHipsterProperties above
    5. #
    6. # More documentation is available at:
    7. # https://www.jhipster.tech/common-application-properties/
    8. # ===================================================================
    9. application:
    10. host: 'http://localhost'
    11. mp-url: 'http://localhost/#/'
    12. merchant-name: '商户名称'

    image.png
    C:\zhuyrWork\外卖项目代码\meal\src\main\java\com\mayocase\meal\service\PayService.java

    1. package com.mayocase.meal.service;
    2. import com.lly835.bestpay.constants.WxPayConstants;
    3. import com.lly835.bestpay.model.PayResponse;
    4. import com.lly835.bestpay.model.RefundResponse;
    5. import com.lly835.bestpay.model.wxpay.WxPayApi;
    6. import com.lly835.bestpay.model.wxpay.response.WxPayAsyncResponse;
    7. import com.lly835.bestpay.model.wxpay.response.WxPayRefundResponse;
    8. import com.lly835.bestpay.model.wxpay.response.WxPaySyncResponse;
    9. import com.lly835.bestpay.service.impl.WxPaySignature;
    10. import com.lly835.bestpay.utils.JsonUtil;
    11. import com.lly835.bestpay.utils.MoneyUtil;
    12. import com.lly835.bestpay.utils.RandomUtil;
    13. import com.lly835.bestpay.utils.XmlUtil;
    14. import com.mayocase.meal.client.PrintClient;
    15. import com.mayocase.meal.config.MyX509TrustManager;
    16. import com.mayocase.meal.domain.SalesOrder;
    17. import com.mayocase.meal.domain.SalesOrderMealItem;
    18. import com.mayocase.meal.domain.SetMealDetails;
    19. import com.mayocase.meal.domain.enumeration.OrderStatus;
    20. import com.mayocase.meal.domain.enumeration.OrderType;
    21. import com.mayocase.meal.service.dto.MealDTO;
    22. import com.mayocase.meal.service.dto.MealInventoryDTO;
    23. import com.mayocase.meal.service.dto.MerchantDTO;
    24. import com.mayocase.meal.service.dto.SalesOrderDTO;
    25. import com.mayocase.meal.service.utls.WXPayUtil;
    26. import com.mayocase.meal.web.rest.errors.ResultEnum;
    27. import com.mayocase.meal.web.rest.errors.SellerException;
    28. import okhttp3.MediaType;
    29. import okhttp3.OkHttpClient;
    30. import okhttp3.RequestBody;
    31. import org.apache.commons.compress.utils.IOUtils;
    32. import org.apache.http.ssl.SSLContexts;
    33. import org.redisson.RedissonMultiLock;
    34. import org.redisson.api.RBucket;
    35. import org.redisson.api.RLock;
    36. import org.redisson.api.RedissonClient;
    37. import org.slf4j.Logger;
    38. import org.slf4j.LoggerFactory;
    39. import org.springframework.beans.BeanUtils;
    40. import org.springframework.beans.factory.annotation.Autowired;
    41. import org.springframework.beans.factory.annotation.Value;
    42. import org.springframework.cloud.cloudfoundry.com.fasterxml.jackson.databind.ObjectMapper;
    43. import org.springframework.orm.ObjectOptimisticLockingFailureException;
    44. import org.springframework.stereotype.Service;
    45. import org.springframework.transaction.annotation.Transactional;
    46. import org.springframework.util.StringUtils;
    47. import retrofit2.Call;
    48. import retrofit2.Response;
    49. import retrofit2.Retrofit;
    50. import retrofit2.converter.simplexml.SimpleXmlConverterFactory;
    51. import javax.net.ssl.SSLContext;
    52. import java.io.File;
    53. import java.io.FileInputStream;
    54. import java.io.IOException;
    55. import java.math.BigDecimal;
    56. import java.security.KeyStore;
    57. import java.time.*;
    58. import java.util.*;
    59. import java.util.concurrent.TimeUnit;
    60. @Service
    61. @Transactional
    62. public class PayService {
    63. private final Logger log = LoggerFactory.getLogger(PayService.class);
    64. private static final String ORDER_NAME = "营养餐";
    65. @Value("${weixin.mp.appID}")
    66. private String wxMPAppID;
    67. @Value("${weixin.mp.mchId}")
    68. private String wxMchId;
    69. @Value("${weixin.mp.notifyUrl}")
    70. private String notifyUrl;
    71. @Value("${weixin.mp.mchKey}")
    72. private String mchKey;
    73. @Value("${weixin.mp.keyPath}")
    74. private String keyPath;
    75. @Autowired
    76. private MealInventoryService mealInventoryService;
    77. @Autowired
    78. private SalesOrderService salesOrderService;
    79. @Autowired
    80. private PrintClient printClient;
    81. @Autowired
    82. private MerchantService merchantService;
    83. @Autowired
    84. private RedissonClient redissonClient;
    85. public RefundResponse refund(SalesOrder order, Integer refundFee) {
    86. Map<String, String> paraMap = new HashMap<String, String>();
    87. paraMap.put("out_trade_no",order.getSn());
    88. paraMap.put("out_refund_no",order.getSn());
    89. paraMap.put("total_fee", order.getPaymentInfo().getTotalPaymentAmount().toString());
    90. paraMap.put("refund_fee", refundFee.toString());
    91. paraMap.put("appid",wxMPAppID);
    92. paraMap.put("mch_id",wxMchId);
    93. paraMap.put("nonce_str", RandomUtil.getRandomStr());
    94. try {
    95. String sign = WXPayUtil.generateSignature(paraMap, mchKey);
    96. paraMap.put("sign", sign);
    97. //初始化证书
    98. this.initSSLContext();
    99. MyX509TrustManager myX509TrustManager = new MyX509TrustManager();
    100. OkHttpClient clinet = new OkHttpClient.Builder()
    101. .sslSocketFactory(sslContext.getSocketFactory(), myX509TrustManager)
    102. .build();
    103. Retrofit retrofit = new Retrofit.Builder()
    104. .baseUrl(WxPayConstants.WXPAY_GATEWAY)
    105. .addConverterFactory(SimpleXmlConverterFactory.create())
    106. .client(clinet)
    107. .build();
    108. String xml = WXPayUtil.mapToXml(paraMap);//将所有参数(map)转xml格式
    109. RequestBody body = RequestBody.create(MediaType.parse("application/xml; charset=utf-8"), xml);
    110. Call<WxPayRefundResponse> call = retrofit.create(WxPayApi.class).refund(body);
    111. Response<WxPayRefundResponse> retrofitResponse = null;
    112. try {
    113. retrofitResponse = call.execute();
    114. } catch (IOException e) {
    115. e.printStackTrace();
    116. }
    117. if (!retrofitResponse.isSuccessful()) {
    118. log.error("【微信退款】发起退款, 网络异常,订单编号:" + order.getSn());
    119. }
    120. WxPayRefundResponse response = retrofitResponse.body();
    121. log.warn("退款结果:" + response.toString());
    122. log.warn("退款结果:" + response);
    123. if (!response.getReturnCode().equals(WxPayConstants.SUCCESS)) {
    124. log.error("【微信退款】发起退款, returnCode != SUCCESS, returnMsg = " + response.getReturnMsg() + ", 订单编号:" + order.getSn());
    125. throw new SellerException(ResultEnum.REFUND_ERROR);
    126. }
    127. if (!response.getResultCode().equals(WxPayConstants.SUCCESS)) {
    128. log.error("【微信退款】发起退款, resultCode != SUCCESS, err_code = " + response.getErrCode() + " err_code_des=" + response.getErrCodeDes() +", 订单编号:" + order.getSn());
    129. log.error("-----------" + response.getErrCodeDes().equals("订单已全额退款"));
    130. //微信公众号人工退款
    131. if(response.getErrCodeDes().equals("订单已全额退款")){
    132. return null;
    133. }else{
    134. throw new SellerException(ResultEnum.REFUND_ERROR);
    135. }
    136. }
    137. return this.buildRefundResponse(response);
    138. }catch (Exception e){
    139. e.printStackTrace();
    140. throw new SellerException(ResultEnum.REFUND_ERROR);
    141. }
    142. }
    143. private PayResponse buildPayResponse(WxPaySyncResponse response) {
    144. String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
    145. String nonceStr = RandomUtil.getRandomStr();
    146. String packAge = "prepay_id=" + response.getPrepayId();
    147. String signType = "MD5";
    148. //先构造要签名的map
    149. Map<String, String> map = new HashMap<>();
    150. map.put("appId", response.getAppid());
    151. map.put("timeStamp", timeStamp);
    152. map.put("nonceStr", nonceStr);
    153. map.put("package", packAge);
    154. map.put("signType", signType);
    155. PayResponse payResponse = new PayResponse();
    156. payResponse.setAppId(response.getAppid());
    157. payResponse.setTimeStamp(timeStamp);
    158. payResponse.setNonceStr(nonceStr);
    159. payResponse.setPackAge(packAge);
    160. payResponse.setSignType(signType);
    161. payResponse.setPaySign(WxPaySignature.sign(map, mchKey));
    162. return payResponse;
    163. }
    164. public PayResponse asyncNotify(String notifyData) {
    165. //xml解析为对象
    166. WxPayAsyncResponse asyncResponse = (WxPayAsyncResponse) XmlUtil.fromXML(notifyData, WxPayAsyncResponse.class);
    167. //签名校验
    168. if (!WxPaySignature.verify(buildMap(asyncResponse), mchKey)) {
    169. log.error("【微信支付异步通知】签名验证失败, response={}", asyncResponse);
    170. throw new RuntimeException("【微信支付异步通知】签名验证失败");
    171. }
    172. if(!asyncResponse.getReturnCode().equals(WxPayConstants.SUCCESS)) {
    173. throw new RuntimeException("【微信支付异步通知】发起支付, returnCode != SUCCESS, returnMsg = " + asyncResponse.getReturnMsg());
    174. }
    175. //该订单已支付直接返回
    176. if (!asyncResponse.getResultCode().equals(WxPayConstants.SUCCESS)
    177. && asyncResponse.getErrCode().equals("ORDERPAID")) {
    178. return buildPayResponse(asyncResponse);
    179. }
    180. if (!asyncResponse.getResultCode().equals(WxPayConstants.SUCCESS)) {
    181. throw new RuntimeException("【微信支付异步通知】发起支付, resultCode != SUCCESS, err_code = " + asyncResponse.getErrCode() + " err_code_des=" + asyncResponse.getErrCodeDes());
    182. }
    183. return buildPayResponse(asyncResponse);
    184. }
    185. public PayResponse create(SalesOrderDTO orderDTO, String openId) {
    186. //拼接统一下单地址参数
    187. Map<String, String> paraMap = new HashMap<String, String>();
    188. //获取请求ip地址
    189. paraMap.put("appid", wxMPAppID);
    190. paraMap.put("body", ORDER_NAME);
    191. paraMap.put("mch_id", wxMchId);
    192. paraMap.put("nonce_str", WXPayUtil.generateNonceStr());
    193. paraMap.put("openid", openId);
    194. paraMap.put("out_trade_no", orderDTO.getSn());//订单号
    195. paraMap.put("spbill_create_ip", "8.8.8.8");
    196. paraMap.put("total_fee", orderDTO.getPaymentInfo().getTotalPaymentAmount().toString());
    197. paraMap.put("trade_type", "JSAPI");
    198. paraMap.put("notify_url",notifyUrl);// 此路径是微信服务器调用支付结果通知路径随意写
    199. try{
    200. String sign = WXPayUtil.generateSignature(paraMap, mchKey);
    201. paraMap.put("sign", sign);
    202. String xml = WXPayUtil.mapToXml(paraMap);//将所有参数(map)转xml格式
    203. log.info("请求参数{}" + xml);
    204. RequestBody body = RequestBody.create(MediaType.parse("application/xml; charset=utf-8"),xml);
    205. Retrofit retrofit = new Retrofit.Builder()
    206. .baseUrl(WxPayConstants.WXPAY_GATEWAY)
    207. .addConverterFactory(SimpleXmlConverterFactory.create())
    208. .build();
    209. Call<WxPaySyncResponse> call = retrofit.create(WxPayApi.class).unifiedorder(body);
    210. Response<WxPaySyncResponse> retrofitResponse = null;
    211. try{
    212. retrofitResponse = call.execute();
    213. }catch (IOException e) {
    214. e.printStackTrace();
    215. }
    216. if (!retrofitResponse.isSuccessful()) {
    217. throw new RuntimeException("【微信统一支付】发起支付, 网络异常");
    218. }
    219. WxPaySyncResponse response = retrofitResponse.body();
    220. log.info("【微信统一支付】response={}", JsonUtil.toJson(response));
    221. if(!response.getReturnCode().equals(WxPayConstants.SUCCESS)) {
    222. throw new RuntimeException("【微信统一支付】发起支付, returnCode != SUCCESS, returnMsg = " + response.getReturnMsg());
    223. }
    224. if (!response.getResultCode().equals(WxPayConstants.SUCCESS)) {
    225. throw new RuntimeException("【微信统一支付】发起支付, resultCode != SUCCESS, err_code = " + response.getErrCode() + " err_code_des=" + response.getErrCodeDes());
    226. }
    227. return this.buildPayResponse(response);
    228. }catch (Exception e){
    229. e.printStackTrace();
    230. throw new SellerException(ResultEnum.PARAM_ERROR.getCode(), "sign失败");
    231. }
    232. }
    233. public void zeroNotify(String sn) throws Exception{
    234. log.info("支付为0开始");
    235. Optional<SalesOrderDTO> op = salesOrderService.getBySn(sn);
    236. //判断订单是否存在
    237. if (!op.isPresent()) {
    238. log.info("【微信支付】异步通知, 订单不存在, orderId={}"+ sn);
    239. throw new SellerException(ResultEnum.ORDER_NOT_EXIST);
    240. }
    241. SalesOrderDTO orderDTO = op.get();
    242. if(orderDTO.getStatus().equals(OrderStatus.PAID)){
    243. return;
    244. }
    245. //修改订单的支付状态和修改实际数量
    246. try {
    247. salesOrderService.paid(orderDTO);
    248. }catch (ObjectOptimisticLockingFailureException s){
    249. op = salesOrderService.getBySn(sn);
    250. salesOrderService.paid(op.get());
    251. }
    252. LocalDateTime create = LocalDateTime.ofInstant(orderDTO.getDiningDate(), ZoneOffset.UTC);
    253. LocalDate localDate = create.toLocalDate();
    254. LocalDateTime localDateTimeNow = LocalDateTime.of(localDate.getYear(), localDate.getMonth(), localDate.getDayOfMonth(),0,0,0);
    255. localDateTimeNow = localDateTimeNow.minusHours(8);
    256. Instant from = localDateTimeNow.toInstant(ZoneOffset.UTC);
    257. log.info("查询reids时间段:" + from);
    258. //当天的去除库存占用,添加到销量
    259. boolean istoday = false;
    260. LocalDate localDate1 = LocalDate.now();
    261. LocalDateTime localDateTime = LocalDateTime.of(localDate1.getYear(), localDate1.getMonth(), localDate1.getDayOfMonth(),0,0,0);
    262. localDateTime = localDateTime.minusHours(8);
    263. Instant todayFrom = localDateTime.toInstant(ZoneOffset.UTC);
    264. Instant todayTo = localDateTime.minusDays(-1).toInstant(ZoneOffset.UTC);
    265. if(orderDTO.getDiningDate().compareTo(todayFrom)>0 && orderDTO.getDiningDate().compareTo(todayTo) <0){
    266. istoday = true;
    267. }
    268. log.info("是否是今天:" + istoday);
    269. RLock rLockStock = redissonClient.getLock("create");
    270. boolean lock = rLockStock.tryLock(10,10,TimeUnit.SECONDS);
    271. if(lock){
    272. Iterator<SalesOrderMealItem> it = orderDTO.getMealItems().iterator();
    273. while (it.hasNext()){
    274. SalesOrderMealItem item = it.next();
    275. if(item.isSetMeal()){
    276. Iterator<SetMealDetails> detailsIterator = item.getSetMealDetails().iterator();
    277. while (detailsIterator.hasNext()){
    278. SetMealDetails details = detailsIterator.next();
    279. RBucket<Map<String,Object>> stock = redissonClient.getBucket(from+orderDTO.getShopSn()+details.getMealPartSn());
    280. Map<String,Object> m = stock.get();
    281. m.put("soldQuantity", (Integer.valueOf(m.get("soldQuantity").toString()) + details.getQuantity()));
    282. if(istoday) {
    283. log.info("当天的去除占用--------------------------------");
    284. m.put("heldQuantity", (Integer.valueOf(m.get("heldQuantity").toString()) - details.getQuantity()));
    285. }
    286. stock.set(m);
    287. }
    288. }else{
    289. RBucket<Map<String,Object>> stock = redissonClient.getBucket(from+orderDTO.getShopSn()+item.getMealSn());
    290. Map<String,Object> m = stock.get();
    291. m.put("soldQuantity", (Integer.valueOf(m.get("soldQuantity").toString()) + item.getQuantity()));
    292. if(istoday) {
    293. log.info("当天的去除占用--------------------------------");
    294. m.put("heldQuantity", (Integer.valueOf(m.get("heldQuantity").toString()) - item.getQuantity()));
    295. }
    296. stock.set(m);
    297. }
    298. }
    299. }
    300. List<MerchantDTO> list = merchantService.findAll();
    301. try {
    302. printClient.print(op.get(), (list == null || list.size() == 0) ? new MerchantDTO() : list.get(0),3);
    303. }catch (Exception e){
    304. e.printStackTrace();
    305. }
    306. log.info("微信回调流程走完");
    307. }
    308. @Transactional
    309. public PayResponse notify(String notifyData) throws Exception{
    310. log.info("微信支付回调{}" + notifyData);
    311. PayResponse payResponse = this.asyncNotify(notifyData);
    312. log.info("【微信支付】异步通知, payResponse={}"+ JsonUtil.toJson(payResponse));
    313. RLock rLock = redissonClient.getLock(payResponse.getOrderId());
    314. try {
    315. rLock.lock();
    316. //查询订单
    317. Optional<SalesOrderDTO> op = salesOrderService.getBySn(payResponse.getOrderId());
    318. //判断订单是否存在
    319. if (!op.isPresent()) {
    320. log.info("【微信支付】异步通知, 订单不存在, orderId={}"+ payResponse.getOrderId());
    321. throw new SellerException(ResultEnum.ORDER_NOT_EXIST);
    322. }
    323. SalesOrderDTO orderDTO = op.get();
    324. if(orderDTO.getStatus().equals(OrderStatus.PAID)){
    325. return payResponse;
    326. }
    327. BigDecimal num1 = new BigDecimal("100");
    328. BigDecimal bigDecimal = new BigDecimal(payResponse.getOrderAmount().toString());
    329. bigDecimal = bigDecimal.multiply(num1);
    330. //判断金额是否一致(0.10 0.1)
    331. BigDecimal b = new BigDecimal(orderDTO.getPaymentInfo().getTotalPaymentAmount());
    332. if (bigDecimal.compareTo(b) != 0) {
    333. log.info("【微信支付】异步通知, 订单金额不一致, orderId={}, 微信通知金额={}, 系统金额={}"+
    334. payResponse.getOrderId()+","+
    335. payResponse.getOrderAmount()+","+
    336. orderDTO.getPaymentInfo().getTotalPaymentAmount());
    337. throw new SellerException(ResultEnum.WXPAY_NOTIFY_MONEY_VERIFY_ERROR);
    338. }
    339. LocalDateTime create = LocalDateTime.ofInstant(orderDTO.getDiningDate(), ZoneOffset.UTC);
    340. LocalDate localDate = create.toLocalDate();
    341. LocalDateTime localDateTimeNow = LocalDateTime.of(localDate.getYear(), localDate.getMonth(), localDate.getDayOfMonth(),0,0,0);
    342. localDateTimeNow = localDateTimeNow.minusHours(8);
    343. Instant from = localDateTimeNow.toInstant(ZoneOffset.UTC);
    344. log.info("查询reids时间段:" + from);
    345. //当天的去除库存占用,添加到销量
    346. boolean istoday = false;
    347. LocalDate localDate1 = LocalDate.now();
    348. LocalDateTime localDateTime = LocalDateTime.of(localDate1.getYear(), localDate1.getMonth(), localDate1.getDayOfMonth(),0,0,0);
    349. localDateTime = localDateTime.minusHours(8);
    350. Instant todayFrom = localDateTime.toInstant(ZoneOffset.UTC);
    351. Instant todayTo = localDateTime.minusDays(-1).toInstant(ZoneOffset.UTC);
    352. if(orderDTO.getDiningDate().compareTo(todayFrom)>0 && orderDTO.getDiningDate().compareTo(todayTo) <0){
    353. istoday = true;
    354. }
    355. log.info("是否是今天:" + istoday);
    356. RLock rLockStock = redissonClient.getLock("create");
    357. boolean lock = rLockStock.tryLock(10,10,TimeUnit.SECONDS);
    358. if(lock){
    359. Iterator<SalesOrderMealItem> it = orderDTO.getMealItems().iterator();
    360. while (it.hasNext()){
    361. SalesOrderMealItem item = it.next();
    362. if(item.isSetMeal()){
    363. Iterator<SetMealDetails> detailsIterator = item.getSetMealDetails().iterator();
    364. while (detailsIterator.hasNext()){
    365. SetMealDetails details = detailsIterator.next();
    366. RBucket<Map<String,Object>> stock = redissonClient.getBucket(from+orderDTO.getShopSn()+details.getMealPartSn());
    367. Map<String,Object> m = stock.get();
    368. m.put("soldQuantity", (Integer.valueOf(m.get("soldQuantity").toString()) + details.getQuantity()*item.getQuantity()));
    369. if(istoday) {
    370. log.info("当天的去除占用--------------------------------");
    371. m.put("heldQuantity", (Integer.valueOf(m.get("heldQuantity").toString()) - details.getQuantity()*item.getQuantity()));
    372. }else{
    373. log.info("预售的添加预售数量--------------------------------");
    374. m.put("reservedQuantity", (Integer.valueOf(m.get("reservedQuantity").toString()) + details.getQuantity()*item.getQuantity()));
    375. }
    376. stock.set(m);
    377. }
    378. }else{
    379. RBucket<Map<String,Object>> stock = redissonClient.getBucket(from+orderDTO.getShopSn()+item.getMealSn());
    380. Map<String,Object> m = stock.get();
    381. m.put("soldQuantity", (Integer.valueOf(m.get("soldQuantity").toString()) + item.getQuantity()));
    382. if(istoday) {
    383. log.info("当天的去除占用--------------------------------");
    384. m.put("heldQuantity", (Integer.valueOf(m.get("heldQuantity").toString()) - item.getQuantity()));
    385. }else{
    386. log.info("预售的添加预售数量--------------------------------");
    387. m.put("reservedQuantity", (Integer.valueOf(m.get("reservedQuantity").toString()) + item.getQuantity()));
    388. }
    389. stock.set(m);
    390. }
    391. }
    392. }
    393. //打印小票
    394. SalesOrder salesOrder = salesOrderService.paid(orderDTO);
    395. List<MerchantDTO> list = merchantService.findAll();
    396. try {
    397. printClient.print(salesOrder, (list == null || list.size() == 0) ? new MerchantDTO() : list.get(0),3);
    398. }catch (Exception e){
    399. e.printStackTrace();
    400. }
    401. log.info("微信回调流程走完");
    402. return payResponse;
    403. }catch (Exception e){
    404. e.printStackTrace();
    405. throw new Exception();
    406. }finally {
    407. rLock.unlock();
    408. }
    409. }
    410. private Map<String, String> buildMap(WxPayAsyncResponse response) {
    411. Map<String, String> map = new HashMap<>();
    412. map.put("return_code", response.getReturnCode());
    413. map.put("return_msg", response.getReturnMsg());
    414. map.put("appid", response.getAppid());
    415. map.put("mch_id", response.getMchId());
    416. map.put("device_info", response.getDeviceInfo());
    417. map.put("nonce_str", response.getNonceStr());
    418. map.put("sign", response.getSign());
    419. map.put("result_code", response.getResultCode());
    420. map.put("err_code", response.getErrCode());
    421. map.put("err_code_des", response.getErrCodeDes());
    422. map.put("openid", response.getOpenid());
    423. map.put("is_subscribe", response.getIsSubscribe());
    424. map.put("trade_type", response.getTradeType());
    425. map.put("bank_type", response.getBankType());
    426. map.put("total_fee", String.valueOf(response.getTotalFee()));
    427. map.put("fee_type", response.getFeeType());
    428. map.put("cash_fee", response.getCashFee());
    429. map.put("cash_fee_type", response.getCashFeeType());
    430. map.put("coupon_fee", response.getCouponFee());
    431. map.put("coupon_count", response.getCouponCount());
    432. map.put("transaction_id", response.getTransactionId());
    433. map.put("out_trade_no", response.getOutTradeNo());
    434. map.put("attach", response.getAttach());
    435. map.put("time_end", response.getTimeEnd());
    436. return map;
    437. }
    438. private PayResponse buildPayResponse(WxPayAsyncResponse response) {
    439. PayResponse payResponse = new PayResponse();
    440. payResponse.setOrderAmount(MoneyUtil.Fen2Yuan(response.getTotalFee()));
    441. payResponse.setOrderId(response.getOutTradeNo());
    442. payResponse.setOutTradeNo(response.getTransactionId());
    443. return payResponse;
    444. }
    445. /**
    446. * 初始化证书
    447. * @return
    448. */
    449. private SSLContext initSSLContext() {
    450. FileInputStream inputStream = null;
    451. try {
    452. String jarPath = this.getClass().getClassLoader().getResource(keyPath).getPath();
    453. log.info("文件路径------------------------" + jarPath);
    454. inputStream = new FileInputStream(new File(jarPath));
    455. } catch (IOException e) {
    456. throw new RuntimeException("读取微信商户证书文件出错", e);
    457. }
    458. try {
    459. KeyStore keystore = KeyStore.getInstance("PKCS12");
    460. char[] partnerId2charArray = wxMchId.toCharArray();
    461. keystore.load(inputStream, partnerId2charArray);
    462. this.sslContext = SSLContexts.custom().loadKeyMaterial(keystore, partnerId2charArray).build();
    463. return this.sslContext;
    464. } catch (Exception e) {
    465. e.printStackTrace();
    466. throw new RuntimeException("证书文件有问题,请核实!", e);
    467. } finally {
    468. IOUtils.closeQuietly(inputStream);
    469. }
    470. }
    471. /**
    472. * 证书内容
    473. */
    474. private SSLContext sslContext;
    475. private RefundResponse buildRefundResponse(WxPayRefundResponse response) {
    476. RefundResponse refundResponse = new RefundResponse();
    477. refundResponse.setOrderId(response.getOutTradeNo());
    478. refundResponse.setOrderAmount(MoneyUtil.Fen2Yuan(response.getTotalFee()));
    479. refundResponse.setOutTradeNo(response.getTransactionId());
    480. refundResponse.setRefundId(response.getOutRefundNo());
    481. refundResponse.setOutRefundNo(response.getRefundId());
    482. return refundResponse;
    483. }
    484. }