OrderServiceImpl.java 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. package com.pay.wxpayback.service.impl;
  2. import cn.hutool.core.date.DateUtil;
  3. import cn.hutool.core.text.StrFormatter;
  4. import cn.hutool.core.util.ObjectUtil;
  5. import cn.hutool.core.util.RandomUtil;
  6. import cn.hutool.core.util.StrUtil;
  7. import cn.hutool.http.HttpUtil;
  8. import cn.hutool.json.JSON;
  9. import cn.hutool.json.JSONObject;
  10. import cn.hutool.json.JSONUtil;
  11. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  12. import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
  13. import com.fasterxml.jackson.databind.JsonNode;
  14. import com.fasterxml.jackson.databind.ObjectMapper;
  15. import com.fasterxml.jackson.databind.node.ObjectNode;
  16. import com.pay.wxpayback.Enum.WxApiType;
  17. import com.pay.wxpayback.Enum.WxPayStatusEnum;
  18. import com.pay.wxpayback.constant.SystemConstant;
  19. import com.pay.wxpayback.constant.wxpay.WXOrderConstant;
  20. import com.pay.wxpayback.constant.wxpay.WechatPayHttpHeaders;
  21. import com.pay.wxpayback.exception.ApiException;
  22. import com.pay.wxpayback.pojo.Order;
  23. import com.pay.wxpayback.mapper.OrderMapper;
  24. import com.pay.wxpayback.pojo.ToolWxConfig;
  25. import com.pay.wxpayback.pojo.vo.ReCreateOrderVO;
  26. import com.pay.wxpayback.pojo.vo.ToCreateOrderVO;
  27. import com.pay.wxpayback.pojo.vo.WxLoginVO;
  28. import com.pay.wxpayback.service.OrderService;
  29. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  30. import com.pay.wxpayback.utils.WxPayUtil;
  31. import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
  32. import lombok.extern.slf4j.Slf4j;
  33. import org.apache.http.client.methods.CloseableHttpResponse;
  34. import org.apache.http.client.methods.HttpGet;
  35. import org.apache.http.client.methods.HttpPost;
  36. import org.apache.http.client.utils.URIBuilder;
  37. import org.apache.http.entity.StringEntity;
  38. import org.apache.http.util.EntityUtils;
  39. import org.springframework.stereotype.Service;
  40. import org.springframework.transaction.annotation.Transactional;
  41. import java.io.ByteArrayInputStream;
  42. import java.io.ByteArrayOutputStream;
  43. import java.io.IOException;
  44. import java.net.URISyntaxException;
  45. import java.nio.charset.StandardCharsets;
  46. import java.security.PrivateKey;
  47. import java.util.HashMap;
  48. import java.util.Map;
  49. import java.util.concurrent.locks.ReentrantLock;
  50. /**
  51. * <p>
  52. * 订单表 服务实现类
  53. * </p>
  54. *
  55. * @author 小王八
  56. * @since 2023-07-28
  57. */
  58. @Service
  59. @Slf4j
  60. public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
  61. private final ReentrantLock reentrantLock1 = new ReentrantLock();
  62. private final ReentrantLock reentrantLock2 = new ReentrantLock();
  63. /**
  64. * 调用微信支付 get请求 统一配置
  65. *要微信签名认证
  66. * @param url
  67. * @return String
  68. * @author zhangjunrong
  69. * @date 2022/5/9 20:39
  70. */
  71. private String getHttpGet(ToolWxConfig wxConfig, String url) throws URISyntaxException, IOException {
  72. //1.构造httpGet请求
  73. URIBuilder uriBuilder = null;
  74. uriBuilder = new URIBuilder(url);
  75. HttpGet httpGet = new HttpGet(uriBuilder.build());
  76. httpGet.addHeader(WechatPayHttpHeaders.ACCEPT, WechatPayHttpHeaders.APPLICATION_JSON);
  77. //2.调起微信查询订单接口
  78. CloseableHttpResponse response = WxPayUtil.getHttpClient(wxConfig, WxPayUtil.getVerifier(wxConfig)).execute(httpGet);
  79. //3.返回结果信息
  80. return EntityUtils.toString(response.getEntity());
  81. }
  82. public WxLoginVO getOpenId(ToolWxConfig wxConfig, WxLoginVO loginVO){
  83. //获取当前的openid
  84. Map hashMap = new HashMap();
  85. hashMap.put("appid", wxConfig.getAppId());
  86. hashMap.put("secret", wxConfig.getAppSecret());
  87. hashMap.put("js_code", loginVO.getCode());
  88. hashMap.put("grant_type", "authorization_code");
  89. String json = HttpUtil.get(WxApiType.WX_LOGIN_URL.getValue(), hashMap);
  90. JSONObject jsonObject = JSONUtil.parseObj(json);
  91. String openid = jsonObject.getStr("openid");
  92. String sessionKey = jsonObject.getStr("session_key");
  93. WxLoginVO result = new WxLoginVO();
  94. result.setOpenId(openid);
  95. result.setSession_key(sessionKey);
  96. return result;
  97. }
  98. @Override
  99. public ReCreateOrderVO createOrder(ToolWxConfig wxConfig, ToCreateOrderVO toCreatOrderVO) {
  100. try {
  101. //1.请求配置参数
  102. HttpPost httpPost = new HttpPost(WxApiType.CREATE_ORDER.getValue());
  103. //格式配置
  104. httpPost.addHeader(WechatPayHttpHeaders.ACCEPT, WechatPayHttpHeaders.APPLICATION_JSON);
  105. httpPost.addHeader(WechatPayHttpHeaders.CONTENT_TYPE, WechatPayHttpHeaders.APPLICATION_JSON_UTF);
  106. //2.读取privateKey私钥
  107. PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(wxConfig.getPrivateKey().getBytes(StandardCharsets.UTF_8)));
  108. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  109. ObjectMapper objectMapper = new ObjectMapper();
  110. //3.配置下单参数
  111. ObjectNode rootNode = objectMapper.createObjectNode();
  112. rootNode.put(WXOrderConstant.MCHID, wxConfig.getMchId())
  113. .put(WXOrderConstant.APPID, wxConfig.getAppId())
  114. .put(WXOrderConstant.DESCRIPTION, toCreatOrderVO.getDescription())
  115. .put(WXOrderConstant.NOTIFY_URL, SystemConstant.PAY_NOTIFY_URL)
  116. .put(WXOrderConstant.OUT_TRADE_NO, toCreatOrderVO.getOutTradeNo());
  117. rootNode.putObject(WXOrderConstant.AMOUNT)
  118. //total 微信需要int类型 为了不丢失精度 单位为分
  119. .put(WXOrderConstant.AMOUNT_TOTAL, toCreatOrderVO.getTotal());
  120. // 用户在直连商户appid下的唯一标识。 下单前需获取到用户的Openid
  121. rootNode.putObject(WXOrderConstant.PAYER)
  122. .put(WXOrderConstant.OPENID, toCreatOrderVO.getOpenId());
  123. objectMapper.writeValue(bos, rootNode);
  124. //4.调用微信下单接口
  125. httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
  126. //接口返回值 申请获取prepay_id
  127. CloseableHttpResponse response = WxPayUtil.getHttpClient(wxConfig, WxPayUtil.getVerifier(wxConfig)).execute(httpPost);
  128. //预支付交易会话标识 prepay_id
  129. String bodyAsString = EntityUtils.toString(response.getEntity());
  130. //5.APP调起支付API 构造签名串
  131. String timestamp = DateUtil.currentSeconds() + "";
  132. String nonce = RandomUtil.randomString(SystemConstant.NUM_32);
  133. StringBuilder builder = new StringBuilder();
  134. //应用id
  135. builder.append(wxConfig.getAppId()).append("\n");
  136. //时间戳
  137. builder.append(timestamp).append("\n");
  138. //随机字符串
  139. builder.append(nonce).append("\n");
  140. //预支付交易会话标识 prepay_id 通过bodyAsString获取 prepay_id
  141. JsonNode node = objectMapper.readTree(bodyAsString);
  142. // prepay_id=wx201410272009395522657a690389285100 必须以此格式
  143. String wxPackage = SystemConstant.WX_PACKAGE + node.get(WXOrderConstant.PREPAY_ID).textValue();
  144. builder.append(wxPackage).append("\n");
  145. log.info("微信签名请求体: {}", builder.toString());
  146. //6.计算签名
  147. String sign = WxPayUtil.sign(builder.toString().getBytes(StandardCharsets.UTF_8), merchantPrivateKey);
  148. //7.返回参数 让前端调微信支付
  149. return new ReCreateOrderVO(timestamp, nonce, wxPackage, "RSA", sign,toCreatOrderVO.getOutTradeNo());
  150. } catch (Exception e) {
  151. log.error(toCreatOrderVO.getOutTradeNo() + "订单下单失败");
  152. log.error(e.toString());
  153. //下单失败抛异常
  154. throw new ApiException("下单失败 重新尝试付款");
  155. }
  156. }
  157. Integer queryAmountByOrderNo(String orderNo) {
  158. QueryWrapper<Order> wrapper = new QueryWrapper<>();
  159. wrapper.select("amount").eq("pk_id",orderNo);
  160. return getOne(wrapper).getAmount();
  161. }
  162. void updateStatus(Integer status,String pkId) {
  163. UpdateWrapper<Order> wrapper = new UpdateWrapper<>();
  164. wrapper.set("pay_status",status).eq("pk_id",pkId);
  165. update(wrapper);
  166. }
  167. Integer getStatus(String pkId) {
  168. QueryWrapper<Order> wrapper = new QueryWrapper<>();
  169. wrapper.select("pay_status").eq("pk_id",pkId);
  170. return getOne(wrapper).getPayStatus();
  171. }
  172. @Override
  173. public Boolean verifyCreateOrder(String decryptOrder) {
  174. //在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
  175. //实现: 加入一把可重入锁
  176. if (reentrantLock1.tryLock()) {
  177. try {
  178. log.info("===================================进入微信支付回调核对订单中========================================");
  179. ObjectMapper objectMapper = new ObjectMapper();
  180. //微信回调 解密后 信息
  181. JsonNode node = objectMapper.readTree(decryptOrder);
  182. //获取订单商户号
  183. String orderNo = node.get(WXOrderConstant.OUT_TRADE_NO).textValue();
  184. log.info(node.get(WXOrderConstant.OUT_TRADE_NO) + "订单回调信息记录:订单状态:" + orderNo);
  185. //2.如果回调 支付类型为成功 核对金额 入数据库
  186. //获取支付状态
  187. String tradeState = node.get(WXOrderConstant.TRADE_STATE).textValue();
  188. if (StrUtil.equals(WXOrderConstant.WX_BACK_OK, tradeState)) {
  189. //1.查询数据库 对比支付金额
  190. if(WxPayUtil.verifyMoney(node, queryAmountByOrderNo(orderNo))) {
  191. //2.对比成功 账单状态 已支付
  192. updateStatus(SystemConstant.NUM_ONE,orderNo);
  193. }
  194. //支付成功 就返回true
  195. return true;
  196. }
  197. } catch (Exception e) {
  198. log.error("订单支付异常===>订单回调信息记录:订单状态:" + decryptOrder);
  199. }finally {
  200. //释放锁
  201. reentrantLock1.unlock();
  202. }
  203. }
  204. return false;
  205. }
  206. @Override
  207. public void closeOrder(ToolWxConfig wxConfig, String outTradeNo) {
  208. try {
  209. //1.微信接口编辑
  210. String url = StrFormatter.format(WxApiType.CLOSE_ORDER.getValue(), outTradeNo);
  211. HttpPost httpPost = new HttpPost(url);
  212. //格式配置
  213. httpPost.addHeader(WechatPayHttpHeaders.ACCEPT, WechatPayHttpHeaders.APPLICATION_JSON);
  214. httpPost.addHeader(WechatPayHttpHeaders.CONTENT_TYPE, WechatPayHttpHeaders.APPLICATION_JSON_UTF);
  215. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  216. //2.添加商户id
  217. ObjectMapper objectMapper = new ObjectMapper();
  218. ObjectNode rootNode = objectMapper.createObjectNode();
  219. rootNode.put(WXOrderConstant.MCHID, wxConfig.getMchId());
  220. objectMapper.writeValue(bos, rootNode);
  221. //3.调起微信关单接口
  222. httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
  223. CloseableHttpResponse response = WxPayUtil.getHttpClient(wxConfig, WxPayUtil.getVerifier(wxConfig)).execute(httpPost);
  224. log.info("=====关单结果======={}====",response);
  225. } catch (Exception e) {
  226. log.error("关单失败" + outTradeNo + e);
  227. }
  228. }
  229. @Override
  230. public String refundOrder(ToolWxConfig wxConfig,Order order) {
  231. try {
  232. //1.请求配置参数
  233. HttpPost httpPost = new HttpPost(WxApiType.REFUND_ORDER.getValue());
  234. //格式配置
  235. httpPost.addHeader(WechatPayHttpHeaders.ACCEPT, WechatPayHttpHeaders.APPLICATION_JSON);
  236. httpPost.addHeader(WechatPayHttpHeaders.CONTENT_TYPE, WechatPayHttpHeaders.APPLICATION_JSON_UTF);
  237. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  238. ObjectMapper objectMapper = new ObjectMapper();
  239. ObjectNode rootNode = objectMapper.createObjectNode();
  240. //2.配置参数 订单商户号 退款订单号 微信订单号(微信生成) 退款回调地址(与下单回调地址不一样) 金额信息 amount: 原订单金额 total 退款金额 refund (单位都是分) 退款币种 CNY 人民币
  241. //商户订单号 下单时生成
  242. rootNode.put(WXOrderConstant.OUT_TRADE_NO, order.getPkId().toString())
  243. //微信支付系统生成的订单号 下单时生成(系统生成)
  244. .put(WXOrderConstant.OUT_REFUND_NO, order.getPkId().toString())
  245. //退款回调地址
  246. .put(WXOrderConstant.NOTIFY_URL, SystemConstant.REFUND_NOTIFY_URL);
  247. //金额信息 amount: 原订单金额 total 退款金额 refund (单位都是分)
  248. rootNode.putObject(WXOrderConstant.AMOUNT)
  249. //现阶段 total==refund 不支持部分退款
  250. //原订单金额 total
  251. .put(WXOrderConstant.AMOUNT_TOTAL, order.getAmount())
  252. //退款金额 refund
  253. .put(WXOrderConstant.AMOUNT_REFUND, order.getAmount())
  254. //退款币种 CNY 人民币
  255. .put(WXOrderConstant.AMOUNT_CURRENCY, SystemConstant.CURRENCY_RMB);
  256. objectMapper.writeValue(bos, rootNode);
  257. //3.调用微信退款接口
  258. httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
  259. //接口返回值
  260. CloseableHttpResponse response = WxPayUtil.getHttpClient(wxConfig, WxPayUtil.getVerifier(wxConfig)).execute(httpPost);
  261. String bodyAsString = EntityUtils.toString(response.getEntity());
  262. log.info("微信申请退款返回结果" + "response:" + bodyAsString);
  263. JsonNode refundNode = objectMapper.readTree(bodyAsString);
  264. // 修改订单退款状态 微信退款入数据库
  265. String status = refundNode.get(WXOrderConstant.STATUS).textValue();
  266. log.info("微信方=====退款状态====={}======",status);
  267. Integer statusCode = WxPayStatusEnum.getCode(status);
  268. updateStatus(statusCode,order.getPkId().toString());
  269. return "退款申请成功 注意退款查收";
  270. } catch (Exception e) {
  271. log.info("微信退款" +order.getPkId() + "失败");
  272. throw new ApiException("退款失败,请联系客服解决");
  273. }
  274. }
  275. @Override
  276. public Boolean updateRefundOrder(String decryptOrder) {
  277. //在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
  278. //实现: 加入一把可重入锁
  279. if (reentrantLock2.tryLock()) {
  280. try {
  281. if (ObjectUtil.isNotEmpty(decryptOrder)) {
  282. ObjectMapper objectMapper = new ObjectMapper();
  283. //string=>json 方便获取数据
  284. JsonNode jsonNode = objectMapper.readTree(decryptOrder);
  285. log.info("微信退款回调参数===================={}====================", decryptOrder);
  286. //获取退款单编号(系统生成)
  287. String outRefundNo = jsonNode.get(WXOrderConstant.OUT_REFUND_NO).textValue();
  288. String refundStatus = jsonNode.get(WXOrderConstant.REFUND_STATUS).textValue();
  289. //数据库核对 如果退款状态已退款说明已处理 直接返回即可
  290. if (getStatus(outRefundNo) == SystemConstant.NUM_THREE) {
  291. return true;
  292. }
  293. Integer statusCode = WxPayStatusEnum.getCode(refundStatus);
  294. log.info("微信退款回调 退款订单编号(系统生成)============{}===========订单状态======{}", outRefundNo, refundStatus);
  295. //1.退款表状态修改 微信退款id(微信生成) 退款 调起退款则给用户退款 不根据本系统回调反馈 所以修改状态不成功 依旧返回true
  296. updateStatus(statusCode,outRefundNo);
  297. return true;
  298. }
  299. } catch (Exception e) {
  300. log.warn("微信退款回调接口====修改订单状态失败" + e);
  301. } finally {
  302. //释放锁
  303. reentrantLock2.unlock();
  304. }
  305. }
  306. return false;
  307. }
  308. @Override
  309. public String queryCreateOrder(ToolWxConfig wxConfig, String outTradeNo) {
  310. try {
  311. //1.查单 微信接口 编辑 微信订单号 + 商户号
  312. String url = StrFormatter.format(WxApiType.QUERY_CREATE_ORDER.getValue(), outTradeNo, wxConfig.getMchId());
  313. //2.调用微信接口
  314. String bodyAsString = getHttpGet(wxConfig, url);
  315. log.info("支付查单信息" + bodyAsString);
  316. //返回查单结果信息
  317. if (ObjectUtil.isNotEmpty(bodyAsString)){
  318. ObjectMapper objectMapper = new ObjectMapper();
  319. JsonNode jsonNode = objectMapper.readTree(bodyAsString);
  320. return jsonNode.get(WXOrderConstant.TRADE_STATE).textValue();
  321. }
  322. } catch (Exception e) {
  323. log.error("支付查单失败" + outTradeNo);
  324. }
  325. return null;
  326. }
  327. @Override
  328. public String queryRefundOrder(ToolWxConfig wxConfig, String refundNo) {
  329. try {
  330. //1.查单 微信接口 拼接url
  331. String url = StrFormatter.format(WxApiType.QUERY_REFUND_ORDER.getValue(), refundNo);
  332. //2.调用微信接口
  333. String bodyAsString = getHttpGet(wxConfig, url);
  334. log.info("退单查询信息============{}========" + bodyAsString);
  335. //返回查单结果信息
  336. if (ObjectUtil.isNotEmpty(bodyAsString)){
  337. ObjectMapper objectMapper = new ObjectMapper();
  338. JsonNode jsonNode = objectMapper.readTree(bodyAsString);
  339. return jsonNode.get(WXOrderConstant.STATUS).textValue();
  340. }
  341. } catch (Exception e) {
  342. log.error("退单查单失败" + refundNo);
  343. }
  344. return null;
  345. }
  346. }