|
@@ -0,0 +1,323 @@
|
|
|
+package com.zanxiang.sdk.service.Impl.pay;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.zanxiang.common.domain.ResultMap;
|
|
|
+import com.zanxiang.common.enums.OrderStateEnum;
|
|
|
+import com.zanxiang.common.enums.ResEnum;
|
|
|
+import com.zanxiang.common.utils.RandomStringUtil;
|
|
|
+import com.zanxiang.sdk.common.util.*;
|
|
|
+import com.zanxiang.sdk.domain.bo.PlatformOrderBO;
|
|
|
+import com.zanxiang.sdk.domain.bo.ProductPayAttachParamBO;
|
|
|
+import com.zanxiang.sdk.domain.bo.ProductPayParamBO;
|
|
|
+import com.zanxiang.sdk.domain.bo.WxPayConfigBO;
|
|
|
+import com.zanxiang.sdk.domain.entity.GamePayWay;
|
|
|
+import com.zanxiang.sdk.service.GamePayWayService;
|
|
|
+import com.zanxiang.sdk.service.OrderPayService;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.jdom.JDOMException;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import weixin.popular.api.SnsAPI;
|
|
|
+
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
+import java.io.*;
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @author xufeng
|
|
|
+ * @date 2022/6/8 15:37
|
|
|
+ */
|
|
|
+@Component
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class WxpayServiceImpl extends PayService implements OrderPayService {
|
|
|
+ private static final Logger logger = LoggerFactory.getLogger(WxpayServiceImpl.class);
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private GamePayWayService gamePayWayService;
|
|
|
+
|
|
|
+ private WxPayConfigBO config;
|
|
|
+
|
|
|
+ private WxPayUtil wxPayUtil;
|
|
|
+
|
|
|
+ private String serverUrl = "http://localhost/";
|
|
|
+
|
|
|
+ private String notifyUrl = "http://localhost/";
|
|
|
+
|
|
|
+ private String filePath = "/tmp/wxpay/";
|
|
|
+
|
|
|
+ private String body;
|
|
|
+ private String totalFee;
|
|
|
+ private String outTradeNo;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 自定义参数
|
|
|
+ */
|
|
|
+ private ProductPayAttachParamBO attach;
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 支付调起
|
|
|
+ *
|
|
|
+ * @param product
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public ResultMap create(ProductPayParamBO product) {
|
|
|
+ System.out.println("WxpayService payCreate");
|
|
|
+
|
|
|
+ this.body = product.getSubject();
|
|
|
+ this.totalFee = String.valueOf(Float.parseFloat(product.getTotalFee()) * 100);
|
|
|
+ this.outTradeNo = product.getOutTradeNo() + RandomStringUtil.randomNumStr(5);
|
|
|
+ this.attach = product.getAttach();
|
|
|
+
|
|
|
+ this.configInit(product.getConfig());
|
|
|
+
|
|
|
+ switch (product.getPayWay()) {
|
|
|
+ case 1:
|
|
|
+ return this.pc(product);
|
|
|
+// return this.mobile(product, "NATIVE");
|
|
|
+ case 2:
|
|
|
+ case 3:
|
|
|
+ return this.mobile(product, "MWEB");
|
|
|
+ case 4:
|
|
|
+ return this.mp(product);
|
|
|
+ default:
|
|
|
+ throw new RuntimeException("未知支付方式");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 异步回调
|
|
|
+ *
|
|
|
+ * @param request
|
|
|
+ * @param response
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public String notify(HttpServletRequest request, HttpServletResponse response) throws IOException, JDOMException {
|
|
|
+ // 读取参数
|
|
|
+ InputStream inputStream = request.getInputStream();
|
|
|
+ StringBuffer sb = new StringBuffer();
|
|
|
+ String s;
|
|
|
+ BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
|
|
|
+ while ((s = in.readLine()) != null) {
|
|
|
+ sb.append(s);
|
|
|
+ }
|
|
|
+ in.close();
|
|
|
+ inputStream.close();
|
|
|
+ logger.info("微信支付回调 body:{}", sb);
|
|
|
+ // 解析xml成map
|
|
|
+ Map<String, String> m = XMLUtil.doXMLParse(sb.toString());
|
|
|
+ // 过滤空 设置 TreeMap
|
|
|
+ SortedMap<Object, Object> packageParams = new TreeMap<>();
|
|
|
+ Iterator it = m.keySet().iterator();
|
|
|
+ while (it.hasNext()) {
|
|
|
+ String parameter = (String) it.next();
|
|
|
+ String parameterValue = m.get(parameter);
|
|
|
+
|
|
|
+ String v = "";
|
|
|
+ if (null != parameterValue) {
|
|
|
+ v = parameterValue.trim();
|
|
|
+ }
|
|
|
+ packageParams.put(parameter, v);
|
|
|
+ }
|
|
|
+ ProductPayAttachParamBO attachBO = this.decodeAttach(packageParams.get("attach"));
|
|
|
+ GamePayWay payInfo = gamePayWayService.getInfo(attachBO.getGamePayWayId());
|
|
|
+ this.config = JSONObject.parseObject(payInfo.getPayConfig(), WxPayConfigBO.class);
|
|
|
+ // 账号信息
|
|
|
+ String key = config.getApiKey(); // key
|
|
|
+ // 判断签名是否正确
|
|
|
+ if (PayCommonUtil.isTenpaySign("UTF-8", packageParams, key)) {
|
|
|
+ logger.info("微信支付成功回调");
|
|
|
+ // ------------------------------
|
|
|
+ // 处理业务开始
|
|
|
+ // ------------------------------
|
|
|
+ String resXml;
|
|
|
+ if ("SUCCESS".equals(packageParams.get("result_code"))) {
|
|
|
+ // 这里是支付成功
|
|
|
+ String orderNo = (String) packageParams.get("out_trade_no");
|
|
|
+ logger.info("微信订单号{}付款成功", orderNo);
|
|
|
+ PlatformOrderBO bo = new PlatformOrderBO();
|
|
|
+ bo.setId(attachBO.getOrderId());
|
|
|
+ bo.setUserId(attachBO.getUserId());
|
|
|
+ bo.setRealAmount(new BigDecimal(packageParams.get("total_amount").toString()));
|
|
|
+ bo.setMerchantOrderNo(request.getParameter("trade_no"));
|
|
|
+ bo.setStatus(OrderStateEnum.SUCCESS.getCode());
|
|
|
+ bo.setGamePaywayId(attachBO.getGamePayWayId());
|
|
|
+ bo.setPayTime(new Date());
|
|
|
+ Boolean result = platformOrderService.pay(bo);
|
|
|
+ if (result) {
|
|
|
+ logger.info("支付宝异步回调成功 request:{},商户订单号为:{}", request, outTradeNo);
|
|
|
+ return ResEnum.SUCCESS.getMsg();
|
|
|
+ }
|
|
|
+ // 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
|
|
|
+ resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
|
|
|
+ } else {
|
|
|
+ logger.info("支付失败,错误信息:{}", packageParams.get("err_code"));
|
|
|
+ resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
|
|
|
+ }
|
|
|
+ // ------------------------------
|
|
|
+ // 处理业务完毕
|
|
|
+ // ------------------------------
|
|
|
+ BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
|
|
|
+ out.write(resXml.getBytes());
|
|
|
+ out.flush();
|
|
|
+ out.close();
|
|
|
+ } else {
|
|
|
+ logger.info("通知签名验证失败");
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同步回调
|
|
|
+ *
|
|
|
+ * @param request
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ResultMap synNotify(HttpServletRequest request) {
|
|
|
+ logger.info("微信支付无同步回调 request:{}", request);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * pc支付
|
|
|
+ *
|
|
|
+ * @param product
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public ResultMap pc(ProductPayParamBO product) {
|
|
|
+ //商户支付回调URL设置指引:进入公众平台-->微信支付-->开发配置-->扫码支付-->修改 加入回调URL
|
|
|
+ SortedMap<Object, Object> packageParams = new TreeMap<>();
|
|
|
+ //封装通用参数
|
|
|
+ wxPayUtil.commonParams(packageParams);
|
|
|
+ packageParams.put("attach", this.attach);
|
|
|
+ //生成签名
|
|
|
+ String sign = PayCommonUtil.createSign("UTF-8", packageParams, config.getApiKey());
|
|
|
+ //组装二维码信息
|
|
|
+ StringBuffer qrCode = new StringBuffer();
|
|
|
+ qrCode.append("weixin://wxpay/bizpayurl?");
|
|
|
+ qrCode.append("appid=" + config.getAppId());
|
|
|
+ qrCode.append("&mch_id=" + config.getMchId());
|
|
|
+ qrCode.append("&nonce_str=" + packageParams.get("nonce_str"));
|
|
|
+// qrCode.append("&attach=" + this.attach);
|
|
|
+ qrCode.append("&out_trade_no=" + this.outTradeNo);
|
|
|
+ qrCode.append("&time_stamp=" + packageParams.get("time_stamp"));
|
|
|
+ qrCode.append("&sign=" + sign);
|
|
|
+ String imgName = this.outTradeNo + ".png";
|
|
|
+ String imgPath = filePath + imgName;
|
|
|
+ logger.info("小程序支付调起,请求参数:qrCode:{}, imgPath:{}", qrCode, imgPath);
|
|
|
+ ZxingUtils.createQRCodeImage(qrCode.toString(), imgPath);
|
|
|
+ return ResultMap.ok(imgPath);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 小程序(需要HTTPS)
|
|
|
+ *
|
|
|
+ * @param product
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public ResultMap mp(ProductPayParamBO product) {
|
|
|
+ //redirect_uri 需要在微信支付端添加认证网址
|
|
|
+ totalFee = wxPayUtil.subZeroAndDot(this.totalFee);
|
|
|
+ String redirect_uri = serverUrl + "weixinMobile/dopay?outTradeNo=" + product.getOutTradeNo() + "&totalFee=" + totalFee;
|
|
|
+ //也可以通过state传递参数 redirect_uri 后面加参数未经过验证
|
|
|
+ String result = SnsAPI.connectOauth2Authorize(config.getAppId(), redirect_uri, true, null);
|
|
|
+ return ResultMap.ok(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * H5手机支付(jspapi)
|
|
|
+ *
|
|
|
+ * @param product
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public ResultMap mobile(ProductPayParamBO product, String trade_type) {
|
|
|
+ logger.info("订单号:{}生成微信支付码", product.getOutTradeNo());
|
|
|
+ try {
|
|
|
+ // 账号信息
|
|
|
+ String key = config.getApiKey();
|
|
|
+// String trade_type = "NATIVE";// 交易类型 原生扫码支付
|
|
|
+// String trade_type = "MWEB";// 交易类型 原生扫码支付
|
|
|
+ SortedMap<Object, Object> packageParams = new TreeMap<>();
|
|
|
+ wxPayUtil.commonParams(packageParams);
|
|
|
+ packageParams.put("body", this.body);// 商品描述
|
|
|
+ packageParams.put("out_trade_no", this.outTradeNo);// 商户订单号
|
|
|
+ totalFee = wxPayUtil.subZeroAndDot(this.totalFee);
|
|
|
+ packageParams.put("total_fee", totalFee);// 总金额
|
|
|
+ packageParams.put("spbill_create_ip", product.getSpbillCreateIp());// 发起人IP地址
|
|
|
+ packageParams.put("notify_url", notifyUrl);// 回调地址
|
|
|
+ packageParams.put("trade_type", trade_type);// 交易类型
|
|
|
+ packageParams.put("attach", JSONObject.toJSONString(this.attach));
|
|
|
+
|
|
|
+// if (trade_type.equals("MWEB")) {
|
|
|
+// //H5支付专用
|
|
|
+// JSONObject value = new JSONObject();
|
|
|
+// value.put("type", "WAP");
|
|
|
+// value.put("wap_url", "https://localhost");////WAP网站URL地址
|
|
|
+// value.put("wap_name", "111");//WAP 网站名
|
|
|
+// JSONObject scene_info = new JSONObject();
|
|
|
+// scene_info.put("h5_info", value);
|
|
|
+// packageParams.put("scene_info", scene_info.toString());
|
|
|
+// }
|
|
|
+ String sign = PayCommonUtil.createSign("UTF-8", packageParams, key);
|
|
|
+ packageParams.put("sign", sign);// 签名
|
|
|
+ logger.info("订单号:{} 加密请求参数 packageParams:{}", product.getOutTradeNo(), packageParams);
|
|
|
+ String requestXML = PayCommonUtil.getRequestXml(packageParams);
|
|
|
+ String resXml = HttpUtil.postData(WxPayUrl.UNIFIED_ORDER_URL, requestXML);
|
|
|
+ Map map = XMLUtil.doXMLParse(resXml);
|
|
|
+ logger.info("订单号:{} 微信响应 map:{}", product.getOutTradeNo(), map);
|
|
|
+ String returnCode = (String) map.get("return_code");
|
|
|
+ if ("SUCCESS".equals(returnCode)) {
|
|
|
+ String resultCode = (String) map.get("result_code");
|
|
|
+ if ("SUCCESS".equals(resultCode)) {
|
|
|
+ logger.info("订单号:{}生成微信支付码成功", product.getOutTradeNo());
|
|
|
+ String urlCode = trade_type.equals("MWEB") ? (String) map.get("mweb_url") : (String) map.get("code_url");
|
|
|
+ String imgName = this.outTradeNo + ".png";
|
|
|
+ String imgPath = filePath + imgName;
|
|
|
+ ZxingUtils.createQRCodeImage(urlCode, imgPath);
|
|
|
+ return ResultMap.ok(imgPath);
|
|
|
+ } else {
|
|
|
+ String errCodeDes = (String) map.get("err_code_des");
|
|
|
+ logger.error("订单号:{}生成微信支付码(系统)失败:{}", product.getOutTradeNo(), errCodeDes);
|
|
|
+ return ResultMap.error(errCodeDes);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ String returnMsg = (String) map.get("return_msg");
|
|
|
+ logger.error("(订单号:{}生成微信支付码(通信)失败:{}", product.getOutTradeNo(), returnMsg);
|
|
|
+ return ResultMap.error(returnMsg);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("订单号:{}生成微信支付码失败(系统异常))", product.getOutTradeNo(), e);
|
|
|
+ return ResultMap.error();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 配置初始化
|
|
|
+ */
|
|
|
+ public void configInit(Object obj) {
|
|
|
+ this.config = JSONObject.parseObject(obj.toString(), WxPayConfigBO.class);
|
|
|
+ this.wxPayUtil = new WxPayUtil(this.config);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 反解析自定义参数
|
|
|
+ *
|
|
|
+ * @param obj
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private ProductPayAttachParamBO decodeAttach(Object obj) {
|
|
|
+ return JSONObject.parseObject(obj.toString(), ProductPayAttachParamBO.class);
|
|
|
+ }
|
|
|
+}
|