|
@@ -0,0 +1,371 @@
|
|
|
+package com.pay.wxpayback.service.impl;
|
|
|
+
|
|
|
+import cn.hutool.core.date.DateUtil;
|
|
|
+import cn.hutool.core.text.StrFormatter;
|
|
|
+import cn.hutool.core.util.ObjectUtil;
|
|
|
+import cn.hutool.core.util.RandomUtil;
|
|
|
+import cn.hutool.core.util.StrUtil;
|
|
|
+import cn.hutool.http.HttpUtil;
|
|
|
+import cn.hutool.json.JSON;
|
|
|
+import cn.hutool.json.JSONObject;
|
|
|
+import cn.hutool.json.JSONUtil;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
|
|
+import com.fasterxml.jackson.databind.JsonNode;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
|
+import com.pay.wxpayback.Enum.WxApiType;
|
|
|
+import com.pay.wxpayback.Enum.WxPayStatusEnum;
|
|
|
+import com.pay.wxpayback.constant.SystemConstant;
|
|
|
+import com.pay.wxpayback.constant.wxpay.WXOrderConstant;
|
|
|
+import com.pay.wxpayback.constant.wxpay.WechatPayHttpHeaders;
|
|
|
+import com.pay.wxpayback.exception.ApiException;
|
|
|
+import com.pay.wxpayback.pojo.Order;
|
|
|
+import com.pay.wxpayback.mapper.OrderMapper;
|
|
|
+import com.pay.wxpayback.pojo.ToolWxConfig;
|
|
|
+import com.pay.wxpayback.pojo.vo.ReCreateOrderVO;
|
|
|
+import com.pay.wxpayback.pojo.vo.ToCreateOrderVO;
|
|
|
+import com.pay.wxpayback.pojo.vo.WxLoginVO;
|
|
|
+import com.pay.wxpayback.service.OrderService;
|
|
|
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import com.pay.wxpayback.utils.WxPayUtil;
|
|
|
+import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.http.client.methods.CloseableHttpResponse;
|
|
|
+import org.apache.http.client.methods.HttpGet;
|
|
|
+import org.apache.http.client.methods.HttpPost;
|
|
|
+import org.apache.http.client.utils.URIBuilder;
|
|
|
+import org.apache.http.entity.StringEntity;
|
|
|
+import org.apache.http.util.EntityUtils;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import java.io.ByteArrayInputStream;
|
|
|
+import java.io.ByteArrayOutputStream;
|
|
|
+import java.io.IOException;
|
|
|
+import java.net.URISyntaxException;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.security.PrivateKey;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.concurrent.locks.ReentrantLock;
|
|
|
+
|
|
|
+
|
|
|
+ * <p>
|
|
|
+ * 订单表 服务实现类
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * @author 小王八
|
|
|
+ * @since 2023-07-28
|
|
|
+ */
|
|
|
+@Service
|
|
|
+@Slf4j
|
|
|
+public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
|
|
|
+
|
|
|
+
|
|
|
+ private final ReentrantLock reentrantLock1 = new ReentrantLock();
|
|
|
+
|
|
|
+ private final ReentrantLock reentrantLock2 = new ReentrantLock();
|
|
|
+
|
|
|
+
|
|
|
+ * 调用微信支付 get请求 统一配置
|
|
|
+ *要微信签名认证
|
|
|
+ * @param url
|
|
|
+ * @return String
|
|
|
+ * @author zhangjunrong
|
|
|
+ * @date 2022/5/9 20:39
|
|
|
+ */
|
|
|
+ private String getHttpGet(ToolWxConfig wxConfig, String url) throws URISyntaxException, IOException {
|
|
|
+
|
|
|
+ URIBuilder uriBuilder = null;
|
|
|
+ uriBuilder = new URIBuilder(url);
|
|
|
+ HttpGet httpGet = new HttpGet(uriBuilder.build());
|
|
|
+ httpGet.addHeader(WechatPayHttpHeaders.ACCEPT, WechatPayHttpHeaders.APPLICATION_JSON);
|
|
|
+
|
|
|
+ CloseableHttpResponse response = WxPayUtil.getHttpClient(wxConfig, WxPayUtil.getVerifier(wxConfig)).execute(httpGet);
|
|
|
+
|
|
|
+ return EntityUtils.toString(response.getEntity());
|
|
|
+ }
|
|
|
+ public WxLoginVO getOpenId(ToolWxConfig wxConfig, WxLoginVO loginVO){
|
|
|
+
|
|
|
+ Map hashMap = new HashMap();
|
|
|
+ hashMap.put("appid", wxConfig.getAppId());
|
|
|
+ hashMap.put("secret", wxConfig.getAppSecret());
|
|
|
+ hashMap.put("js_code", loginVO.getCode());
|
|
|
+ hashMap.put("grant_type", "authorization_code");
|
|
|
+ String json = HttpUtil.get(WxApiType.WX_LOGIN_URL.getValue(), hashMap);
|
|
|
+ JSONObject jsonObject = JSONUtil.parseObj(json);
|
|
|
+ String openid = jsonObject.getStr("openid");
|
|
|
+ String sessionKey = jsonObject.getStr("session_key");
|
|
|
+ WxLoginVO result = new WxLoginVO();
|
|
|
+ result.setOpenId(openid);
|
|
|
+ result.setSession_key(sessionKey);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ReCreateOrderVO createOrder(ToolWxConfig wxConfig, ToCreateOrderVO toCreatOrderVO) {
|
|
|
+ try {
|
|
|
+
|
|
|
+ HttpPost httpPost = new HttpPost(WxApiType.CREATE_ORDER.getValue());
|
|
|
+
|
|
|
+ httpPost.addHeader(WechatPayHttpHeaders.ACCEPT, WechatPayHttpHeaders.APPLICATION_JSON);
|
|
|
+ httpPost.addHeader(WechatPayHttpHeaders.CONTENT_TYPE, WechatPayHttpHeaders.APPLICATION_JSON_UTF);
|
|
|
+
|
|
|
+ PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(wxConfig.getPrivateKey().getBytes(StandardCharsets.UTF_8)));
|
|
|
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
|
+ ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+
|
|
|
+ ObjectNode rootNode = objectMapper.createObjectNode();
|
|
|
+ rootNode.put(WXOrderConstant.MCHID, wxConfig.getMchId())
|
|
|
+ .put(WXOrderConstant.APPID, wxConfig.getAppId())
|
|
|
+ .put(WXOrderConstant.DESCRIPTION, toCreatOrderVO.getDescription())
|
|
|
+ .put(WXOrderConstant.NOTIFY_URL, SystemConstant.PAY_NOTIFY_URL)
|
|
|
+ .put(WXOrderConstant.OUT_TRADE_NO, toCreatOrderVO.getOutTradeNo());
|
|
|
+ rootNode.putObject(WXOrderConstant.AMOUNT)
|
|
|
+
|
|
|
+ .put(WXOrderConstant.AMOUNT_TOTAL, toCreatOrderVO.getTotal());
|
|
|
+
|
|
|
+ rootNode.putObject(WXOrderConstant.PAYER)
|
|
|
+ .put(WXOrderConstant.OPENID, toCreatOrderVO.getOpenId());
|
|
|
+
|
|
|
+ objectMapper.writeValue(bos, rootNode);
|
|
|
+
|
|
|
+ httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
|
|
|
+
|
|
|
+ CloseableHttpResponse response = WxPayUtil.getHttpClient(wxConfig, WxPayUtil.getVerifier(wxConfig)).execute(httpPost);
|
|
|
+
|
|
|
+ String bodyAsString = EntityUtils.toString(response.getEntity());
|
|
|
+
|
|
|
+ String timestamp = DateUtil.currentSeconds() + "";
|
|
|
+ String nonce = RandomUtil.randomString(SystemConstant.NUM_32);
|
|
|
+ StringBuilder builder = new StringBuilder();
|
|
|
+
|
|
|
+ builder.append(wxConfig.getAppId()).append("\n");
|
|
|
+
|
|
|
+ builder.append(timestamp).append("\n");
|
|
|
+
|
|
|
+ builder.append(nonce).append("\n");
|
|
|
+
|
|
|
+ JsonNode node = objectMapper.readTree(bodyAsString);
|
|
|
+
|
|
|
+ String wxPackage = SystemConstant.WX_PACKAGE + node.get(WXOrderConstant.PREPAY_ID).textValue();
|
|
|
+ builder.append(wxPackage).append("\n");
|
|
|
+
|
|
|
+ log.info("微信签名请求体: {}", builder.toString());
|
|
|
+
|
|
|
+
|
|
|
+ String sign = WxPayUtil.sign(builder.toString().getBytes(StandardCharsets.UTF_8), merchantPrivateKey);
|
|
|
+
|
|
|
+
|
|
|
+ return new ReCreateOrderVO(timestamp, nonce, wxPackage, "RSA", sign,toCreatOrderVO.getOutTradeNo());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error(toCreatOrderVO.getOutTradeNo() + "订单下单失败");
|
|
|
+ log.error(e.toString());
|
|
|
+
|
|
|
+ throw new ApiException("下单失败 重新尝试付款");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Integer queryAmountByOrderNo(String orderNo) {
|
|
|
+ QueryWrapper<Order> wrapper = new QueryWrapper<>();
|
|
|
+ wrapper.select("amount").eq("pk_id",orderNo);
|
|
|
+ return getOne(wrapper).getAmount();
|
|
|
+ }
|
|
|
+
|
|
|
+ void updateStatus(Integer status,String pkId) {
|
|
|
+ UpdateWrapper<Order> wrapper = new UpdateWrapper<>();
|
|
|
+ wrapper.set("pay_status",status).eq("pk_id",pkId);
|
|
|
+ update(wrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ Integer getStatus(String pkId) {
|
|
|
+ QueryWrapper<Order> wrapper = new QueryWrapper<>();
|
|
|
+ wrapper.select("pay_status").eq("pk_id",pkId);
|
|
|
+ return getOne(wrapper).getPayStatus();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Boolean verifyCreateOrder(String decryptOrder) {
|
|
|
+
|
|
|
+
|
|
|
+ if (reentrantLock1.tryLock()) {
|
|
|
+ try {
|
|
|
+ log.info("===================================进入微信支付回调核对订单中========================================");
|
|
|
+ ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+
|
|
|
+ JsonNode node = objectMapper.readTree(decryptOrder);
|
|
|
+
|
|
|
+ String orderNo = node.get(WXOrderConstant.OUT_TRADE_NO).textValue();
|
|
|
+
|
|
|
+ log.info(node.get(WXOrderConstant.OUT_TRADE_NO) + "订单回调信息记录:订单状态:" + orderNo);
|
|
|
+
|
|
|
+
|
|
|
+ String tradeState = node.get(WXOrderConstant.TRADE_STATE).textValue();
|
|
|
+ if (StrUtil.equals(WXOrderConstant.WX_BACK_OK, tradeState)) {
|
|
|
+
|
|
|
+ if(WxPayUtil.verifyMoney(node, queryAmountByOrderNo(orderNo))) {
|
|
|
+
|
|
|
+ updateStatus(SystemConstant.NUM_ONE,orderNo);
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("订单支付异常===>订单回调信息记录:订单状态:" + decryptOrder);
|
|
|
+ }finally {
|
|
|
+
|
|
|
+ reentrantLock1.unlock();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void closeOrder(ToolWxConfig wxConfig, String outTradeNo) {
|
|
|
+ try {
|
|
|
+
|
|
|
+ String url = StrFormatter.format(WxApiType.CLOSE_ORDER.getValue(), outTradeNo);
|
|
|
+ HttpPost httpPost = new HttpPost(url);
|
|
|
+
|
|
|
+ httpPost.addHeader(WechatPayHttpHeaders.ACCEPT, WechatPayHttpHeaders.APPLICATION_JSON);
|
|
|
+ httpPost.addHeader(WechatPayHttpHeaders.CONTENT_TYPE, WechatPayHttpHeaders.APPLICATION_JSON_UTF);
|
|
|
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
|
+
|
|
|
+ ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+ ObjectNode rootNode = objectMapper.createObjectNode();
|
|
|
+ rootNode.put(WXOrderConstant.MCHID, wxConfig.getMchId());
|
|
|
+ objectMapper.writeValue(bos, rootNode);
|
|
|
+
|
|
|
+ httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
|
|
|
+ CloseableHttpResponse response = WxPayUtil.getHttpClient(wxConfig, WxPayUtil.getVerifier(wxConfig)).execute(httpPost);
|
|
|
+ log.info("=====关单结果======={}====",response);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("关单失败" + outTradeNo + e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String refundOrder(ToolWxConfig wxConfig,Order order) {
|
|
|
+ try {
|
|
|
+
|
|
|
+ HttpPost httpPost = new HttpPost(WxApiType.REFUND_ORDER.getValue());
|
|
|
+
|
|
|
+ httpPost.addHeader(WechatPayHttpHeaders.ACCEPT, WechatPayHttpHeaders.APPLICATION_JSON);
|
|
|
+ httpPost.addHeader(WechatPayHttpHeaders.CONTENT_TYPE, WechatPayHttpHeaders.APPLICATION_JSON_UTF);
|
|
|
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
|
+ ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+ ObjectNode rootNode = objectMapper.createObjectNode();
|
|
|
+
|
|
|
+
|
|
|
+ rootNode.put(WXOrderConstant.OUT_TRADE_NO, order.getPkId().toString())
|
|
|
+
|
|
|
+ .put(WXOrderConstant.OUT_REFUND_NO, order.getPkId().toString())
|
|
|
+
|
|
|
+ .put(WXOrderConstant.NOTIFY_URL, SystemConstant.REFUND_NOTIFY_URL);
|
|
|
+
|
|
|
+ rootNode.putObject(WXOrderConstant.AMOUNT)
|
|
|
+
|
|
|
+
|
|
|
+ .put(WXOrderConstant.AMOUNT_TOTAL, order.getAmount())
|
|
|
+
|
|
|
+ .put(WXOrderConstant.AMOUNT_REFUND, order.getAmount())
|
|
|
+
|
|
|
+ .put(WXOrderConstant.AMOUNT_CURRENCY, SystemConstant.CURRENCY_RMB);
|
|
|
+ objectMapper.writeValue(bos, rootNode);
|
|
|
+
|
|
|
+ httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
|
|
|
+
|
|
|
+ CloseableHttpResponse response = WxPayUtil.getHttpClient(wxConfig, WxPayUtil.getVerifier(wxConfig)).execute(httpPost);
|
|
|
+ String bodyAsString = EntityUtils.toString(response.getEntity());
|
|
|
+ log.info("微信申请退款返回结果" + "response:" + bodyAsString);
|
|
|
+ JsonNode refundNode = objectMapper.readTree(bodyAsString);
|
|
|
+
|
|
|
+ String status = refundNode.get(WXOrderConstant.STATUS).textValue();
|
|
|
+ log.info("微信方=====退款状态====={}======",status);
|
|
|
+ Integer statusCode = WxPayStatusEnum.getCode(status);
|
|
|
+ updateStatus(statusCode,order.getPkId().toString());
|
|
|
+ return "退款申请成功 注意退款查收";
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.info("微信退款" +order.getPkId() + "失败");
|
|
|
+ throw new ApiException("退款失败,请联系客服解决");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Boolean updateRefundOrder(String decryptOrder) {
|
|
|
+
|
|
|
+
|
|
|
+ if (reentrantLock2.tryLock()) {
|
|
|
+ try {
|
|
|
+ if (ObjectUtil.isNotEmpty(decryptOrder)) {
|
|
|
+ ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+
|
|
|
+ JsonNode jsonNode = objectMapper.readTree(decryptOrder);
|
|
|
+ log.info("微信退款回调参数===================={}====================", decryptOrder);
|
|
|
+
|
|
|
+ String outRefundNo = jsonNode.get(WXOrderConstant.OUT_REFUND_NO).textValue();
|
|
|
+ String refundStatus = jsonNode.get(WXOrderConstant.REFUND_STATUS).textValue();
|
|
|
+
|
|
|
+ if (getStatus(outRefundNo) == SystemConstant.NUM_THREE) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ Integer statusCode = WxPayStatusEnum.getCode(refundStatus);
|
|
|
+ log.info("微信退款回调 退款订单编号(系统生成)============{}===========订单状态======{}", outRefundNo, refundStatus);
|
|
|
+
|
|
|
+ updateStatus(statusCode,outRefundNo);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("微信退款回调接口====修改订单状态失败" + e);
|
|
|
+ } finally {
|
|
|
+
|
|
|
+ reentrantLock2.unlock();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String queryCreateOrder(ToolWxConfig wxConfig, String outTradeNo) {
|
|
|
+ try {
|
|
|
+
|
|
|
+ String url = StrFormatter.format(WxApiType.QUERY_CREATE_ORDER.getValue(), outTradeNo, wxConfig.getMchId());
|
|
|
+
|
|
|
+ String bodyAsString = getHttpGet(wxConfig, url);
|
|
|
+ log.info("支付查单信息" + bodyAsString);
|
|
|
+
|
|
|
+ if (ObjectUtil.isNotEmpty(bodyAsString)){
|
|
|
+ ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+ JsonNode jsonNode = objectMapper.readTree(bodyAsString);
|
|
|
+ return jsonNode.get(WXOrderConstant.TRADE_STATE).textValue();
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("支付查单失败" + outTradeNo);
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String queryRefundOrder(ToolWxConfig wxConfig, String refundNo) {
|
|
|
+ try {
|
|
|
+
|
|
|
+ String url = StrFormatter.format(WxApiType.QUERY_REFUND_ORDER.getValue(), refundNo);
|
|
|
+
|
|
|
+ String bodyAsString = getHttpGet(wxConfig, url);
|
|
|
+ log.info("退单查询信息============{}========" + bodyAsString);
|
|
|
+
|
|
|
+ if (ObjectUtil.isNotEmpty(bodyAsString)){
|
|
|
+ ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+ JsonNode jsonNode = objectMapper.readTree(bodyAsString);
|
|
|
+ return jsonNode.get(WXOrderConstant.STATUS).textValue();
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("退单查单失败" + refundNo);
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+}
|