|
|
@@ -1,9 +1,13 @@
|
|
|
package com.yingyangfly.core.util;
|
|
|
|
|
|
import cn.hutool.core.date.DateUtil;
|
|
|
+import cn.hutool.core.util.StrUtil;
|
|
|
+import cn.hutool.json.JSONUtil;
|
|
|
import com.alibaba.fastjson2.JSON;
|
|
|
import com.alibaba.fastjson2.JSONObject;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.google.gson.Gson;
|
|
|
+import com.google.gson.JsonObject;
|
|
|
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
|
|
|
import com.wechat.pay.contrib.apache.httpclient.auth.*;
|
|
|
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
|
|
|
@@ -12,9 +16,11 @@ import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler
|
|
|
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
|
|
|
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
|
|
|
import com.yingyangfly.common.utils.ids.IdUtils;
|
|
|
+import com.yingyangfly.core.config.WechatConfig;
|
|
|
import com.yingyangfly.core.domain.OrgMerchant;
|
|
|
import com.yingyangfly.core.domain.PayOrder;
|
|
|
import com.yingyangfly.core.service.OrgMerchantService;
|
|
|
+import com.yingyangfly.core.vo.WxPayJsApiPreVO;
|
|
|
import lombok.SneakyThrows;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
@@ -27,15 +33,21 @@ import org.apache.http.util.EntityUtils;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.stereotype.Component;
|
|
|
|
|
|
+import javax.annotation.Resource;
|
|
|
import javax.servlet.ServletInputStream;
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
import java.io.BufferedReader;
|
|
|
import java.io.ByteArrayInputStream;
|
|
|
import java.io.IOException;
|
|
|
import java.io.InputStreamReader;
|
|
|
+import java.net.HttpURLConnection;
|
|
|
+import java.net.URL;
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
import java.security.PrivateKey;
|
|
|
+import java.util.ArrayList;
|
|
|
import java.util.Calendar;
|
|
|
+import java.util.List;
|
|
|
+import java.util.UUID;
|
|
|
|
|
|
/**
|
|
|
* @author wanghaiyang
|
|
|
@@ -50,6 +62,9 @@ public class WXPayUtil {
|
|
|
private CloseableHttpClient httpClient;
|
|
|
|
|
|
|
|
|
+ @Resource
|
|
|
+ private WechatConfig wechatConfig;
|
|
|
+
|
|
|
/**
|
|
|
* 获取微信回调结果
|
|
|
*
|
|
|
@@ -143,7 +158,7 @@ public class WXPayUtil {
|
|
|
*
|
|
|
* @return 私钥对象
|
|
|
*/
|
|
|
- public PrivateKey getPrivateKey(String privateKey ) throws IOException {
|
|
|
+ public PrivateKey getPrivateKey(String privateKey) throws IOException {
|
|
|
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
|
|
|
new ByteArrayInputStream(privateKey.getBytes()));
|
|
|
return merchantPrivateKey;
|
|
|
@@ -230,6 +245,196 @@ public class WXPayUtil {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 微信JSAPI 下单接口
|
|
|
+ * @param payOrder
|
|
|
+ * @param Code
|
|
|
+ * @return
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ public String getWXJSAPIPay(PayOrder payOrder,String Code)throws Exception{
|
|
|
+ log.info("订单信息:{}",payOrder);
|
|
|
+ //根据orgCode获取商户信息
|
|
|
+ OrgMerchant orgMer = new OrgMerchant();
|
|
|
+ orgMer.setType("1");
|
|
|
+ orgMer.setOrgCode("system");
|
|
|
+ OrgMerchant orgMerchant = orgMerchantService.selectOne(orgMer);
|
|
|
+ String privateKey = orgMerchant.getPrivateKey().replace("-----BEGIN PRIVATE KEY-----", "")
|
|
|
+ .replace("-----END PRIVATE KEY-----", "")
|
|
|
+ .replaceAll("\\s+", "");
|
|
|
+ String appId= orgMerchant.getAppId();
|
|
|
+ String mchId = orgMerchant.getMchId();
|
|
|
+ String mchSerialNo= orgMerchant.getMchSerialNo();
|
|
|
+ String apiV3Key= orgMerchant.getApiKey();
|
|
|
+ String wxNotifyUrl= orgMerchant.getNotifyUrl();
|
|
|
+ String wxReturnNotifyUrl= orgMerchant.getReturnNotifyUrl();
|
|
|
+ String gateway= orgMerchant.getGateway();
|
|
|
+ // 修改1:接口地址改为JSAPI
|
|
|
+ HttpPost httpPost = new HttpPost(orgMerchant.getGateway() + "/v3/pay/transactions/jsapi");
|
|
|
+ // 下单接口金额单位为分
|
|
|
+ log.info("下单金额:{}",payOrder.getPayMoney());
|
|
|
+ Long payMoney = payOrder.getPayMoney();
|
|
|
+ log.info("下单金额:{}",payMoney);
|
|
|
+ //获取10分钟之后的时间,作为订单的结束时间
|
|
|
+ Calendar nowTime = Calendar.getInstance();
|
|
|
+ nowTime.add(Calendar.MINUTE, 10);
|
|
|
+ String format = DateUtil.format(nowTime.getTime(), "yyyy-MM-dd'T'HH:mm:ssXXX");
|
|
|
+
|
|
|
+ // 请求body参数
|
|
|
+ // 支付成功回调通知接口 notify_url,必须为https
|
|
|
+ // 修改2:请求body中增加 payer.openid 参数(JSAPI必传)
|
|
|
+ String reqdata = "{" +
|
|
|
+ "\"time_expire\": \"" + format + "\"," +
|
|
|
+ "\"amount\": {" +
|
|
|
+ "\"total\": " + payMoney + "," +
|
|
|
+ "\"currency\": \"CNY\"" +
|
|
|
+ "}," +
|
|
|
+ "\"mchid\": \"" + mchId + "\"," +
|
|
|
+ "\"out_trade_no\": \"" + payOrder.getPayOrderId() + "\"," +
|
|
|
+ "\"appid\": \"" + appId + "\"," +
|
|
|
+ "\"description\": \"" + payOrder.getDescription() + "\"," +
|
|
|
+ "\"attach\": \"" + "payOrder.getAttach()" + "\"," +
|
|
|
+ // --- JSAPI 新增参数 ---
|
|
|
+ "\"payer\": {" +
|
|
|
+ "\"openid\": \"" + getOpenId(Code) + "\"" + // 必须传递用户的openid
|
|
|
+ "}," +
|
|
|
+ // ---------------------
|
|
|
+ "\"notify_url\": \"" + wxNotifyUrl + "/m" + mchId + "\"" +
|
|
|
+ "}";
|
|
|
+
|
|
|
+ StringEntity entity = new StringEntity(reqdata, "utf-8");
|
|
|
+ entity.setContentType("application/json");
|
|
|
+ httpPost.setEntity(entity);
|
|
|
+ httpPost.setHeader("Accept", "application/json");
|
|
|
+
|
|
|
+ // 加载商户私钥(privateKey:私钥字符串)
|
|
|
+ PrivateKey merchantPrivateKey = PemUtil
|
|
|
+ .loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
|
|
|
+
|
|
|
+ // 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
|
|
|
+ AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
|
|
|
+ new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3Key.getBytes("utf-8"));
|
|
|
+
|
|
|
+ // 初始化httpClient
|
|
|
+ httpClient = WechatPayHttpClientBuilder.create()
|
|
|
+ .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
|
|
|
+ .withValidator(new WechatPay2Validator(verifier)).build();
|
|
|
+ //完成签名并执行请求
|
|
|
+ CloseableHttpResponse response = httpClient.execute(httpPost);
|
|
|
+ try {
|
|
|
+ int statusCode = response.getStatusLine().getStatusCode();
|
|
|
+ //处理成功
|
|
|
+ if (statusCode == 200) {
|
|
|
+ String prepayId = EntityUtils.toString(response.getEntity());
|
|
|
+ JSONObject jsonObject = JSON.parseObject(prepayId);
|
|
|
+ log.info("success,return body = " + EntityUtils.toString(response.getEntity()));
|
|
|
+ return jsonObject.getString("prepay_id");
|
|
|
+ } else {
|
|
|
+ log.info("微信支付JSAPI下单失败:statusCode{},响应体:{}", statusCode, EntityUtils.toString(response.getEntity()));
|
|
|
+ throw new IOException("换取微信二维码异常");
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ response.close();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 微信JSAPI 获取调起支付参数
|
|
|
+ * @param prepayId
|
|
|
+ * @return
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ public WxPayJsApiPreVO convertWxPayJsApiPreInfo(String prepayId) throws Exception {
|
|
|
+ OrgMerchant orgMer = new OrgMerchant();
|
|
|
+ orgMer.setType("1");
|
|
|
+ orgMer.setOrgCode("system");
|
|
|
+ OrgMerchant orgMerchant = orgMerchantService.selectOne(orgMer);
|
|
|
+
|
|
|
+ String appId = orgMerchant.getAppId();
|
|
|
+ String privateKey = orgMerchant.getPrivateKey();
|
|
|
+ if (StringUtils.isEmpty(prepayId)) {
|
|
|
+ throw new IllegalArgumentException("prepayId不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 构建基础参数(时间戳、随机字符串、package)
|
|
|
+ Long timestamp = System.currentTimeMillis(); // 秒级时间戳
|
|
|
+ String nonceStr = UUID.randomUUID().toString().replace("-", ""); // 32位随机字符串
|
|
|
+ String packageStr = "prepay_id=" + prepayId; // 固定格式
|
|
|
+
|
|
|
+ // 3. 构建签名参数列表(按字段名ASCII字典序排序)
|
|
|
+// List<String> signParams = new ArrayList<>();
|
|
|
+// signParams.add(appId); // 公众号/小程序appId
|
|
|
+// signParams.add(timeStamp); // 时间戳
|
|
|
+// signParams.add(nonceStr); // 随机字符串
|
|
|
+// signParams.add(packageStr); // prepay_id=xxx
|
|
|
+
|
|
|
+ // 4. 生成签名
|
|
|
+// String signMessage;
|
|
|
+// String paySign;
|
|
|
+// try {
|
|
|
+// signMessage = WxPayJsApiSignUtil.buildSignMessage(signParams); // 拼接签名字符串
|
|
|
+// paySign = WxPayJsApiSignUtil.signV3(signMessage.getBytes(StandardCharsets.UTF_8),privateKey); // SHA256-RSA签名
|
|
|
+// log.debug("[微信JSAPI支付] 签名原始串: {}", signMessage);
|
|
|
+// } catch (Exception e) {
|
|
|
+// log.error("[微信JSAPI支付] 签名生成失败: {}", e.getMessage());
|
|
|
+// throw new RuntimeException("支付签名生成失败", e);
|
|
|
+// }
|
|
|
+ String paySign = WxPayJsApiSignUtil.getSign(appId , timestamp, nonceStr, packageStr,privateKey);//签名
|
|
|
+ log.info("paySignpaySignpaySign="+paySign);
|
|
|
+
|
|
|
+ // 5. 构造返回对象
|
|
|
+ WxPayJsApiPreVO result = new WxPayJsApiPreVO();
|
|
|
+ result.setWxAppId(appId); // 公众号/小程序ID
|
|
|
+ result.setWxTimeStamp(timestamp); // 时间戳
|
|
|
+ result.setWxNonceStr(nonceStr); // 随机字符串
|
|
|
+ result.setWxPackage(packageStr); // 固定格式:prepay_id=xxx
|
|
|
+ result.setWxSignType("RSA"); // 签名类型(固定RSA)
|
|
|
+ result.setWxPaySign(paySign); // 签名
|
|
|
+
|
|
|
+ // 6. 日志记录结果
|
|
|
+ log.info("[微信JSAPI支付] 预支付参数构建完成: {}", JSONUtil.toJsonStr(result));
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取openid
|
|
|
+ * @param code
|
|
|
+ * @return
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ public String getOpenId(String code) throws Exception {
|
|
|
+ // 构建请求URL
|
|
|
+ String requestUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" +wechatConfig.getAppId()
|
|
|
+ + "&secret=" + wechatConfig.getAppSecret()
|
|
|
+ + "&code=" + code
|
|
|
+ + "&grant_type=authorization_code";
|
|
|
+
|
|
|
+ // 发送HTTP GET请求
|
|
|
+ URL url = new URL(requestUrl);
|
|
|
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
|
|
+ connection.setRequestMethod("GET");
|
|
|
+ int responseCode = connection.getResponseCode();
|
|
|
+
|
|
|
+ if (responseCode == HttpURLConnection.HTTP_OK) { // 请求成功
|
|
|
+ BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
|
|
+ StringBuilder response = new StringBuilder();
|
|
|
+ String line;
|
|
|
+ while ((line = in.readLine()) != null) {
|
|
|
+ response.append(line);
|
|
|
+ }
|
|
|
+ in.close();
|
|
|
+
|
|
|
+ // 解析JSON响应,获取openid
|
|
|
+ String jsonResponse = response.toString();
|
|
|
+ JsonObject jsonObject = new Gson().fromJson(jsonResponse, JsonObject.class);
|
|
|
+ String openid = jsonObject.get("openid").getAsString();
|
|
|
+ return openid;
|
|
|
+ } else {
|
|
|
+ throw new Exception("HTTP GET请求失败:" + responseCode);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
@SneakyThrows
|
|
|
public JSONObject queryOrder(PayOrder pay){
|
|
|
OrgMerchant orgMerchant = orgMerchantService.getOrgMerchant(pay.getOrgCode(),"1");
|