|
@@ -0,0 +1,568 @@
|
|
|
+package com.yami.shop.wx.service.impl;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.alibaba.fastjson2.JSONObject;
|
|
|
+import com.alibaba.fastjson2.TypeReference;
|
|
|
+import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
|
|
|
+import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
|
|
|
+import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
|
|
|
+import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
|
|
|
+import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
|
|
|
+import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
|
|
|
+import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
|
|
|
+import com.yami.shop.wx.config.CombinePayUrlEnum;
|
|
|
+import com.yami.shop.wx.config.WxConstants;
|
|
|
+import com.yami.shop.wx.po.JsapiPo;
|
|
|
+import com.yami.shop.wx.service.WxProviderService;
|
|
|
+import com.yami.shop.wx.utils.HttpUtils;
|
|
|
+import com.yami.shop.wx.utils.OrderUtils;
|
|
|
+import com.yami.shop.wx.utils.WechatPayValidator;
|
|
|
+import lombok.SneakyThrows;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.http.HttpEntity;
|
|
|
+import org.apache.http.HttpStatus;
|
|
|
+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.entity.StringEntity;
|
|
|
+import org.apache.http.impl.client.CloseableHttpClient;
|
|
|
+import org.apache.http.util.EntityUtils;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
+import java.io.BufferedWriter;
|
|
|
+import java.io.FileWriter;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.InputStream;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.security.PrivateKey;
|
|
|
+import java.security.Signature;
|
|
|
+import java.util.Base64;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.concurrent.locks.ReentrantLock;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class WxProviderServiceImpl implements WxProviderService {
|
|
|
+
|
|
|
+ private final ReentrantLock lock = new ReentrantLock();
|
|
|
+
|
|
|
+ public synchronized Map<String, Object> subJsapi(JsapiPo po) {
|
|
|
+ System.out.println("微信支付传入参数===========" + po);
|
|
|
+ String type = "sub_jsapi";
|
|
|
+ Map<String, Object> params = new HashMap<>(8);
|
|
|
+ params.put("sp_appid", WxConstants.SP_APP_ID);
|
|
|
+ params.put("sp_mchid", WxConstants.SP_MCH_ID);
|
|
|
+ params.put("sub_appid", WxConstants.SUB_APP_ID);
|
|
|
+ params.put("sub_mchid", WxConstants.SUB_MCH_ID);
|
|
|
+ Map<String, Object> payerMap = new HashMap<>(4);
|
|
|
+ payerMap.put("sub_openid", po.getOpenId());
|
|
|
+ params.put("payer", payerMap);
|
|
|
+ params.put("description", po.getDescription());
|
|
|
+ params.put("out_trade_no", po.getOutTradeNo());
|
|
|
+ params.put("notify_url", WxConstants.NOTIFY_URL);
|
|
|
+ JSONObject attachJson = po.getAttachJson();
|
|
|
+ if (attachJson != null) {
|
|
|
+ params.put("attach", JSONObject.toJSONString(attachJson));
|
|
|
+ }
|
|
|
+ Map<String, Object> amountMap = new HashMap<>(4);
|
|
|
+ amountMap.put("total", po.getTotal());
|
|
|
+ amountMap.put("currency", "CNY");
|
|
|
+ params.put("amount", amountMap);
|
|
|
+ Map<String, Object> sceneInfoMap = new HashMap<>(4);
|
|
|
+ sceneInfoMap.put("payer_client_ip", "127.0.0.1");
|
|
|
+ sceneInfoMap.put("device_id", "127.0.0.1");
|
|
|
+ params.put("scene_info", sceneInfoMap);
|
|
|
+ String paramsStr = JSON.toJSONString(params);
|
|
|
+ log.info("请求参数 ===> {}" + paramsStr);
|
|
|
+ String[] split = type.split("_");
|
|
|
+ String newType = split[split.length - 1];
|
|
|
+ String url = WxConstants.BASE_URL.concat(CombinePayUrlEnum.PAY_TRANSACTIONS.getType().concat(newType));
|
|
|
+ log.info("请求地址 ===> {}" + url);
|
|
|
+ String resStr = wechatHttpPost(url, paramsStr);
|
|
|
+ Map<String, Object> resMap = JSONObject.parseObject(resStr, new TypeReference<Map<String, Object>>() {
|
|
|
+ });
|
|
|
+ Map<String, Object> signMap = paySignMsgApplet(resMap);
|
|
|
+ resMap.put("type", type);
|
|
|
+ resMap.put("signMap", signMap);
|
|
|
+ return resMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建签名
|
|
|
+ *
|
|
|
+ * @param map 参数
|
|
|
+ * @return map
|
|
|
+ */
|
|
|
+ private Map<String, Object> paySignMsgApplet(Map<String, Object> map) {
|
|
|
+ long timeMillis = System.currentTimeMillis();
|
|
|
+ String timeStamp = timeMillis / 1000 + "";
|
|
|
+ String nonceStr = String.valueOf(timeMillis);
|
|
|
+ String prepayId = map.get("prepay_id").toString();
|
|
|
+ String packageStr = "prepay_id=" + prepayId;
|
|
|
+ Map<String, Object> resMap = new HashMap<>();
|
|
|
+ resMap.put("nonceStr", nonceStr);
|
|
|
+ resMap.put("timeStamp", timeStamp);
|
|
|
+ resMap.put("appId", WxConstants.SP_APP_ID);
|
|
|
+ resMap.put("package", packageStr);
|
|
|
+ resMap.put("paySign", getWxPayResultMap(prepayId, timeStamp, nonceStr));
|
|
|
+ resMap.put("signType", "RSA");
|
|
|
+ return resMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ @SneakyThrows
|
|
|
+ public String getWxPayResultMap(String prepayId, String timestamp, String nonceStr) {
|
|
|
+ Signature sign = Signature.getInstance("SHA256withRSA");
|
|
|
+ PrivateKey privateKey = getPrivateKey(WxConstants.KEY_PEM_PATH);
|
|
|
+ sign.initSign(privateKey);
|
|
|
+ String sb = WxConstants.SUB_APP_ID + "\n" +
|
|
|
+ timestamp + "\n" +
|
|
|
+ nonceStr + "\n" +
|
|
|
+ "prepay_id=" + prepayId + "\n";
|
|
|
+ sign.update(sb.getBytes(StandardCharsets.UTF_8));
|
|
|
+ return Base64.getEncoder().encodeToString(sign.sign());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 关闭(取消)订单
|
|
|
+ *
|
|
|
+ * @param orderNo orderNo
|
|
|
+ */
|
|
|
+ public void closeOrder(String orderNo) {
|
|
|
+ // TODO 用于在客户下单后,不进行支付,取消订单的场景
|
|
|
+ log.info("根据订单号取消订单,订单号: {}", orderNo);
|
|
|
+ String url = String.format(CombinePayUrlEnum.CLOSE_ORDER_BY_NO.getType(), orderNo);
|
|
|
+ url = WxConstants.BASE_URL.concat(url);
|
|
|
+ Map<String, String> params = new HashMap<>(2);
|
|
|
+ params.put("sp_mchid", WxConstants.SP_MCH_ID);
|
|
|
+ params.put("sub_mchid", WxConstants.SUB_MCH_ID);
|
|
|
+ String paramsStr = JSON.toJSONString(params);
|
|
|
+ log.info("请求参数 ===> {}" + paramsStr);
|
|
|
+ String res = wechatHttpPost(url, paramsStr);
|
|
|
+ log.info(res);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 申请退款
|
|
|
+ *
|
|
|
+ * @param orderNo orderNo
|
|
|
+ * @return orderNo
|
|
|
+ */
|
|
|
+ public String refundOrder(String orderNo) {
|
|
|
+ Integer total = 0;
|
|
|
+ log.info("根据订单号申请退款,订单号: {}", orderNo);
|
|
|
+ String url = WxConstants.BASE_URL.concat(CombinePayUrlEnum.DOMESTIC_REFUNDS.getType());
|
|
|
+ Map<String, Object> params = new HashMap<>(2);
|
|
|
+ params.put("out_trade_no", orderNo);
|
|
|
+ params.put("sub_mchid", WxConstants.SUB_MCH_ID);
|
|
|
+ String outRefundNo = OrderUtils.getOrderNo("TK");
|
|
|
+ log.info("退款申请号:{}", outRefundNo);
|
|
|
+ params.put("out_refund_no", outRefundNo + "");
|
|
|
+ params.put("reason", "申请退款");
|
|
|
+ params.put("notify_url", WxConstants.REFUND_NOTIFY_URL);
|
|
|
+ Map<String, Object> amountMap = new HashMap<>();
|
|
|
+ //退款金额,单位:分
|
|
|
+ amountMap.put("refund", total);
|
|
|
+ //原订单金额,单位:分
|
|
|
+ amountMap.put("total", total);
|
|
|
+ amountMap.put("currency", "CNY");
|
|
|
+ params.put("amount", amountMap);
|
|
|
+ String paramsStr = JSON.toJSONString(params);
|
|
|
+ log.info("请求参数 ===> {}" + paramsStr);
|
|
|
+ String res;
|
|
|
+ try {
|
|
|
+ res = wechatHttpPost(url, paramsStr);
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new RuntimeException(e.getMessage());
|
|
|
+ }
|
|
|
+ log.info("退款结果:{}", res);
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询单笔退款信息
|
|
|
+ *
|
|
|
+ * @param refundNo refundNo
|
|
|
+ * @return return
|
|
|
+ */
|
|
|
+ public Map<String, Object> queryRefundOrder(String refundNo) {
|
|
|
+ log.info("根据订单号查询退款订单,订单号: {}", refundNo);
|
|
|
+ String url = WxConstants.BASE_URL
|
|
|
+ .concat(CombinePayUrlEnum.DOMESTIC_REFUNDS_QUERY.getType().concat(refundNo))
|
|
|
+ .concat("?sub_mchid=").concat(WxConstants.SUB_MCH_ID);
|
|
|
+ String res = wechatHttpGet(url);
|
|
|
+ log.info("查询退款订单结果:{}", res);
|
|
|
+ Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>() {
|
|
|
+ });
|
|
|
+ String successTime = resMap.get("success_time").toString();
|
|
|
+ String refundId = resMap.get("refund_id").toString();
|
|
|
+ /*
|
|
|
+ 款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往商户平台-交易中心,手动处理此笔退款。
|
|
|
+ 枚举值:
|
|
|
+ SUCCESS:退款成功
|
|
|
+ CLOSED:退款关闭
|
|
|
+ PROCESSING:退款处理中
|
|
|
+ ABNORMAL:退款异常
|
|
|
+ */
|
|
|
+ String status = resMap.get("status").toString();
|
|
|
+ /*
|
|
|
+ 枚举值:
|
|
|
+ ORIGINAL:原路退款
|
|
|
+ BALANCE:退回到余额
|
|
|
+ OTHER_BALANCE:原账户异常退到其他余额账户
|
|
|
+ OTHER_BANKCARD:原银行卡异常退到其他银行卡
|
|
|
+ */
|
|
|
+ String channel = resMap.get("channel").toString();
|
|
|
+ String userReceivedAccount = resMap.get("user_received_account").toString();
|
|
|
+ log.info("successTime:" + successTime);
|
|
|
+ log.info("channel:" + channel);
|
|
|
+ log.info("refundId:" + refundId);
|
|
|
+ log.info("status:" + status);
|
|
|
+ log.info("userReceivedAccount:" + userReceivedAccount);
|
|
|
+ // TODO 在查询单笔退款信息时,可以再去查询一次订单的状态,保证该订单已经退款完毕了
|
|
|
+ return resMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 申请交易账单
|
|
|
+ *
|
|
|
+ * @param billDate 格式yyyy-MM-dd 仅支持三个月内的账单下载申请 ,如果传入日期未为当天则会出错
|
|
|
+ * @param billType 分为:ALL、SUCCESS、REFUND
|
|
|
+ * ALL:返回当日所有订单信息(不含充值退款订单)
|
|
|
+ * SUCCESS:返回当日成功支付的订单(不含充值退款订单)
|
|
|
+ * REFUND:返回当日退款订单(不含充值退款订单)
|
|
|
+ * @return 结果
|
|
|
+ */
|
|
|
+ public String tradeBill(String billDate, String billType) {
|
|
|
+ log.info("申请交易账单,billDate:{},billType:{}", billDate, billType);
|
|
|
+ String url = WxConstants.BASE_URL.concat(CombinePayUrlEnum.TRADE_BILLS.getType())
|
|
|
+ .concat("?bill_date=").concat(billDate).concat("&bill_type=").concat(billType);
|
|
|
+ // 填则默认返回服务商下的交易或退款数据,下载某个子商户下的交易或退款数据,则该字段必填
|
|
|
+ url = url.concat("&sub_mchid=").concat(WxConstants.SUB_MCH_ID);
|
|
|
+ String res = wechatHttpGet(url);
|
|
|
+ log.info("查询退款订单结果:{}", res);
|
|
|
+ Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>() {
|
|
|
+ });
|
|
|
+ return resMap.get("download_url").toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param billDate 格式yyyy-MM-dd 仅支持三个月内的账单下载申请,如果传入日期未为当天则会出错
|
|
|
+ * @param accountType 分为:BASIC、OPERATION、FEES
|
|
|
+ * BASIC:基本账户
|
|
|
+ * OPERATION:运营账户
|
|
|
+ * FEES:手续费账户
|
|
|
+ * @return 结果
|
|
|
+ */
|
|
|
+ public String fundFlowBill(String billDate, String accountType) {
|
|
|
+ log.info("申请交易账单,billDate:{},accountType:{}", billDate, accountType);
|
|
|
+ String url = WxConstants.BASE_URL.concat(CombinePayUrlEnum.FUND_FLOW_BILLS.getType())
|
|
|
+ .concat("?bill_date=").concat(billDate).concat("&account_type=").concat(accountType);
|
|
|
+ String res = wechatHttpGet(url);
|
|
|
+ log.info("查询退款订单结果:{}", res);
|
|
|
+ Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>() {
|
|
|
+ });
|
|
|
+ return resMap.get("download_url").toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param billDate 格式yyyy-MM-dd 仅支持三个月内的账单下载申请,如果传入日期未为当天则会出错
|
|
|
+ * @param accountType 分为:BASIC、OPERATION、FEES
|
|
|
+ * BASIC:基本账户
|
|
|
+ * OPERATION:运营账户
|
|
|
+ * FEES:手续费账户
|
|
|
+ * @return 结果
|
|
|
+ */
|
|
|
+ public String subMerchantFundFlowBill(String billDate, String accountType) {
|
|
|
+ log.info("申请单个子商户资金账单,billDate:{},accountType:{}", billDate, accountType);
|
|
|
+ String url = WxConstants.BASE_URL.concat(CombinePayUrlEnum.FUND_FLOW_BILLS.getType())
|
|
|
+ .concat("?bill_date=").concat(billDate).concat("&account_type=").concat(accountType)
|
|
|
+ .concat("&sub_mchid=").concat(billDate).concat("&algorithm=").concat("AEAD_AES_256_GCM");
|
|
|
+ String res = wechatHttpGet(url);
|
|
|
+ log.info("查询退款订单结果:{}", res);
|
|
|
+ Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>() {
|
|
|
+ });
|
|
|
+ String downloadBillCount = resMap.get("download_bill_count").toString();
|
|
|
+ String downloadBillList = resMap.get("download_bill_list").toString();
|
|
|
+ List<Map<String, Object>> billListMap = JSONObject.parseObject(downloadBillList, new TypeReference<List<Map<String, Object>>>() {
|
|
|
+ });
|
|
|
+ String downloadUrl = billListMap.get(0).get("download_url").toString();
|
|
|
+ log.info("downloadBillCount=" + downloadBillCount);
|
|
|
+ log.info("downloadUrl=" + downloadUrl);
|
|
|
+ return downloadUrl;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 下载账单
|
|
|
+ *
|
|
|
+ * @param downloadUrl downloadUrl
|
|
|
+ */
|
|
|
+ public void downloadBill(String downloadUrl) {
|
|
|
+ log.info("下载账单,下载地址:{}", downloadUrl);
|
|
|
+ HttpGet httpGet = new HttpGet(downloadUrl);
|
|
|
+ httpGet.addHeader("Accept", "application/json");
|
|
|
+ CloseableHttpResponse response = null;
|
|
|
+ try {
|
|
|
+ response = noSignHttpClient().execute(httpGet);
|
|
|
+ String body = EntityUtils.toString(response.getEntity());
|
|
|
+ int statusCode = response.getStatusLine().getStatusCode();
|
|
|
+ if (statusCode == 200 || statusCode == 204) {
|
|
|
+ log.info("下载账单,返回结果 = " + body);
|
|
|
+ } else {
|
|
|
+ throw new RuntimeException("下载账单异常, 响应码 = " + statusCode + ", 下载账单返回结果 = " + body);
|
|
|
+ }
|
|
|
+ // TODO 将body内容转为excel存入本地或者输出到浏览器,演示存入本地
|
|
|
+ writeStringToFile(body);
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new RuntimeException(e.getMessage());
|
|
|
+ } finally {
|
|
|
+ if (response != null) {
|
|
|
+ try {
|
|
|
+ response.close();
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 文件写入
|
|
|
+ *
|
|
|
+ * @param body body
|
|
|
+ */
|
|
|
+ private void writeStringToFile(String body) {
|
|
|
+ FileWriter fw = null;
|
|
|
+ try {
|
|
|
+ String filePath = "C:\\Users\\lhz12\\Desktop\\wxPay.txt";
|
|
|
+ fw = new FileWriter(filePath, true);
|
|
|
+ BufferedWriter bw = new BufferedWriter(fw);
|
|
|
+ bw.write(body);
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ } finally {
|
|
|
+ try {
|
|
|
+ if (fw != null) {
|
|
|
+ fw.close();
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取商户的私钥文件
|
|
|
+ *
|
|
|
+ * @param keyPemPath keyPemPath
|
|
|
+ * @return return
|
|
|
+ */
|
|
|
+ public PrivateKey getPrivateKey(String keyPemPath) {
|
|
|
+ InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(keyPemPath);
|
|
|
+ if (inputStream == null) {
|
|
|
+ throw new RuntimeException("私钥文件不存在");
|
|
|
+ }
|
|
|
+ return PemUtil.loadPrivateKey(inputStream);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取证书管理器实例
|
|
|
+ *
|
|
|
+ * @return return
|
|
|
+ */
|
|
|
+ @SneakyThrows
|
|
|
+ public Verifier getVerifier() {
|
|
|
+ log.info("获取证书管理器实例");
|
|
|
+ //获取商户私钥
|
|
|
+ PrivateKey privateKey = getPrivateKey(WxConstants.KEY_PEM_PATH);
|
|
|
+ //私钥签名对象
|
|
|
+ PrivateKeySigner privateKeySigner = new PrivateKeySigner(WxConstants.SERIAL_NO, privateKey);
|
|
|
+ //身份认证对象
|
|
|
+ WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(WxConstants.SP_MCH_ID, privateKeySigner);
|
|
|
+ // 使用定时更新的签名验证器,不需要传入证书
|
|
|
+ CertificatesManager certificatesManager = CertificatesManager.getInstance();
|
|
|
+ certificatesManager.putMerchant(WxConstants.SP_MCH_ID,
|
|
|
+ wechatPay2Credentials, WxConstants.API_V3_KEY.getBytes(StandardCharsets.UTF_8));
|
|
|
+ return certificatesManager.getVerifier(WxConstants.SP_MCH_ID);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取支付http请求对象
|
|
|
+ *
|
|
|
+ * @return return
|
|
|
+ */
|
|
|
+ public CloseableHttpClient httpClient() {
|
|
|
+ String keyPemPath = WxConstants.KEY_PEM_PATH;
|
|
|
+ PrivateKey privateKey = getPrivateKey(keyPemPath);
|
|
|
+ WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
|
|
|
+ .withMerchant(WxConstants.SP_MCH_ID, WxConstants.SERIAL_NO, privateKey)
|
|
|
+ .withValidator(new WechatPay2Validator(getVerifier()));
|
|
|
+ // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
|
|
|
+ return builder.build();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取HttpClient,无需进行应答签名验证,跳过验签的流程
|
|
|
+ */
|
|
|
+ public CloseableHttpClient noSignHttpClient() {
|
|
|
+ //获取商户私钥
|
|
|
+ PrivateKey privateKey = getPrivateKey(WxConstants.KEY_PEM_PATH);
|
|
|
+ //用于构造HttpClient
|
|
|
+ WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
|
|
|
+ //设置商户信息
|
|
|
+ .withMerchant(WxConstants.SP_MCH_ID, WxConstants.SERIAL_NO, privateKey)
|
|
|
+ //无需进行签名验证、通过withValidator((response) -> true)实现
|
|
|
+ .withValidator((response) -> true);
|
|
|
+ // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
|
|
|
+ return builder.build();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发送get请求
|
|
|
+ *
|
|
|
+ * @param url 请求地址
|
|
|
+ * @return 结果
|
|
|
+ */
|
|
|
+ public String wechatHttpGet(String url) {
|
|
|
+ CloseableHttpClient wxPayClient = httpClient();
|
|
|
+ try {
|
|
|
+ HttpGet httpGet = new HttpGet(url);
|
|
|
+ httpGet.setHeader("Accept", "application/json");
|
|
|
+ CloseableHttpResponse response = wxPayClient.execute(httpGet);
|
|
|
+ return getResponseBody(response);
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new RuntimeException(e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发送post请求
|
|
|
+ *
|
|
|
+ * @param url 请求地址
|
|
|
+ * @param paramsStr 参数
|
|
|
+ * @return 结果
|
|
|
+ */
|
|
|
+ public String wechatHttpPost(String url, String paramsStr) {
|
|
|
+ CloseableHttpClient wxPayClient = httpClient();
|
|
|
+ try {
|
|
|
+ HttpPost httpPost = new HttpPost(url);
|
|
|
+ StringEntity entity = new StringEntity(paramsStr, "utf-8");
|
|
|
+ entity.setContentType("application/json");
|
|
|
+ httpPost.setEntity(entity);
|
|
|
+ httpPost.setHeader("Accept", "application/json");
|
|
|
+ CloseableHttpResponse response = wxPayClient.execute(httpPost);
|
|
|
+ return getResponseBody(response);
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new RuntimeException(e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 读取响应的请求体
|
|
|
+ *
|
|
|
+ * @param response 响应
|
|
|
+ * @return 响应
|
|
|
+ * @throws IOException IOException
|
|
|
+ */
|
|
|
+ private String getResponseBody(CloseableHttpResponse response) throws IOException {
|
|
|
+ HttpEntity entity = response.getEntity();
|
|
|
+ String body = entity == null ? "" : EntityUtils.toString(entity);
|
|
|
+ int statusCode = response.getStatusLine().getStatusCode();
|
|
|
+ if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) {
|
|
|
+ log.info("成功, 返回结果 = " + body);
|
|
|
+ } else {
|
|
|
+ String msg = "微信支付请求失败,响应码 = " + statusCode + ",返回结果 = " + body;
|
|
|
+ log.error(msg);
|
|
|
+ throw new RuntimeException(msg);
|
|
|
+ }
|
|
|
+ return body;
|
|
|
+ }
|
|
|
+
|
|
|
+ public JSONObject jsapiNotify(HttpServletRequest request, HttpServletResponse response) {
|
|
|
+ JSONObject bodyJson = getNotifyBodyJson(request);
|
|
|
+ if (bodyJson == null) {
|
|
|
+ return falseMsg(response);
|
|
|
+ }
|
|
|
+ if (lock.tryLock()) {
|
|
|
+ try {
|
|
|
+ // 解密resource中的通知数据
|
|
|
+ String resource = bodyJson.getString("resource");
|
|
|
+ JSONObject resourceJson = WechatPayValidator.decryptFromResource(resource, WxConstants.API_V3_KEY, 2);
|
|
|
+ log.info(" =================== 服务商小程序支付回调解密resource中的通知数据 ===================\n" + resourceJson);
|
|
|
+ Integer trans = statusTrans(resourceJson.getString("trade_state"));
|
|
|
+ JSONObject attach = resourceJson.getJSONObject("attach");
|
|
|
+ if (trans == 1 && attach != null) {
|
|
|
+ JSONObject successJson = trueMsg(response);
|
|
|
+ successJson.put("attach", attach);
|
|
|
+ return successJson;
|
|
|
+ } else {
|
|
|
+ System.err.println("支付失败...");
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ lock.unlock();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return trueMsg(response);
|
|
|
+ }
|
|
|
+
|
|
|
+ private JSONObject getNotifyBodyJson(HttpServletRequest request) {
|
|
|
+ String body = HttpUtils.readData(request);
|
|
|
+ log.info("===========微信回调参数===========\n" + body);
|
|
|
+ log.info("微信回调参数:{}", body);
|
|
|
+ JSONObject jsonObject = JSONObject.parseObject(body);
|
|
|
+ WechatPayValidator wechatPayValidator
|
|
|
+ = new WechatPayValidator(getVerifier(), jsonObject.getString("id"), body);
|
|
|
+ if (!wechatPayValidator.validate(request)) {
|
|
|
+ log.error("通知验签失败");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ log.info("通知验签成功");
|
|
|
+ return jsonObject;
|
|
|
+ }
|
|
|
+
|
|
|
+ private JSONObject falseMsg(HttpServletResponse response) {
|
|
|
+ JSONObject resMap = new JSONObject();
|
|
|
+ response.setStatus(500);
|
|
|
+ resMap.put("code", "ERROR");
|
|
|
+ resMap.put("message", "通知验签失败");
|
|
|
+ return resMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 支付状态( 1-支付成功 )
|
|
|
+ *
|
|
|
+ * @param tradeState 微信返回支付状态码
|
|
|
+ * @return 状态
|
|
|
+ */
|
|
|
+ private Integer statusTrans(String tradeState) {
|
|
|
+ int payStatus;
|
|
|
+ if ("SUCCESS".equals(tradeState)) {
|
|
|
+ payStatus = 1;
|
|
|
+ } else if ("NOTPAY".equals(tradeState)) {
|
|
|
+ payStatus = 0;
|
|
|
+ } else if ("REVOKED".equals(tradeState)) {
|
|
|
+ payStatus = 4;
|
|
|
+ } else if ("CLOSED".equals(tradeState)) {
|
|
|
+ payStatus = 6;
|
|
|
+ } else if ("PAYERROR".equals(tradeState)) {
|
|
|
+ payStatus = 5;
|
|
|
+ } else {
|
|
|
+ payStatus = 8;
|
|
|
+ }
|
|
|
+ return payStatus;
|
|
|
+ }
|
|
|
+
|
|
|
+ private JSONObject trueMsg(HttpServletResponse response) {
|
|
|
+ JSONObject resMap = new JSONObject();
|
|
|
+ //成功应答
|
|
|
+ response.setStatus(200);
|
|
|
+ resMap.put("code", "SUCCESS");
|
|
|
+ resMap.put("message", "成功");
|
|
|
+ return resMap;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|