mirror of
https://gitee.com/elegant_wings/dbd-meeting.git
synced 2025-06-21 05:39:36 +08:00
描述:微信支付
This commit is contained in:
parent
5ea57ed52d
commit
c1296add2e
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,12 +1,12 @@
|
|||||||
# Compiled class file
|
# Compiled class file
|
||||||
*.class
|
*.class
|
||||||
|
*.iml
|
||||||
# Log file
|
# Log file
|
||||||
*.log
|
*.log
|
||||||
|
*.idea
|
||||||
# BlueJ files
|
# BlueJ files
|
||||||
*.ctxt
|
*.ctxt
|
||||||
|
*/target
|
||||||
# Mobile Tools for Java (J2ME)
|
# Mobile Tools for Java (J2ME)
|
||||||
.mtj.tmp/
|
.mtj.tmp/
|
||||||
|
|
||||||
|
@ -60,6 +60,13 @@
|
|||||||
<version>3.11.0</version>
|
<version>3.11.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 微信支付 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.wechatpay-apiv3</groupId>
|
||||||
|
<artifactId>wechatpay-apache-httpclient</artifactId>
|
||||||
|
<version>0.4.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
@ -0,0 +1,87 @@
|
|||||||
|
package com.ics.admin.controller.meeting;
|
||||||
|
|
||||||
|
import com.ics.admin.domain.meeting.CustomerTicket;
|
||||||
|
import com.ics.admin.pay.wx.WxPayCallbackUtil;
|
||||||
|
import com.ics.admin.pay.wx.WxPayCommon;
|
||||||
|
import com.ics.admin.pay.wx.dto.WxChatBasePayDto;
|
||||||
|
import com.ics.admin.pay.wx.dto.WxChatPayDto;
|
||||||
|
import com.ics.admin.service.meeting.ICustomerTicketService;
|
||||||
|
import com.ics.common.core.controller.BaseController;
|
||||||
|
import com.ics.common.core.domain.R;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/meeting/wxPay")
|
||||||
|
public class WxPayController extends BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ICustomerTicketService customerTicketService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
WxPayCommon wxPayCommon;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
WxPayCallbackUtil wxPayCallbackUtil;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载
|
||||||
|
*/
|
||||||
|
@PostMapping("placeOrder")
|
||||||
|
public R placeOrder(@RequestBody WxChatBasePayDto wxChatBasePayDto) {
|
||||||
|
String prepayId = wxPayCommon.wxJsApiPay(wxChatBasePayDto);
|
||||||
|
WxChatPayDto wxChatPayDto = wxPayCommon.wxPayCall(prepayId);
|
||||||
|
return R.data(wxChatPayDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单支付后回调
|
||||||
|
*/
|
||||||
|
@PostMapping("orderCallBack")
|
||||||
|
public HashMap orderCallBack(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
HashMap<String, String> returnParam = new HashMap<>();
|
||||||
|
try {
|
||||||
|
Map<String, String> parseParam = wxPayCallbackUtil.wxChatPayCallback(request, response);
|
||||||
|
// 交易状态
|
||||||
|
// SUCCESS:支付成功
|
||||||
|
// REFUND:转入退款
|
||||||
|
// NOTPAY:未支付
|
||||||
|
// CLOSED:已关闭
|
||||||
|
// REVOKED:已撤销(付款码支付)
|
||||||
|
// USERPAYING:用户支付中(付款码支付)
|
||||||
|
// PAYERROR:支付失败(其他原因,如银行返回失败)
|
||||||
|
String trade_state = parseParam.get("trade_state");
|
||||||
|
// 商户订单号
|
||||||
|
String out_trade_no = parseParam.get("out_trade_no");
|
||||||
|
// 微信订单号
|
||||||
|
String transaction_id = parseParam.get("transaction_id");
|
||||||
|
//
|
||||||
|
//支付成功
|
||||||
|
if (trade_state.equals("SUCCESS")){
|
||||||
|
//编写支付成功后逻辑
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
//响应微信
|
||||||
|
returnParam.put("code", "SUCCESS");
|
||||||
|
returnParam.put("message", "成功");
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return returnParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,157 @@
|
|||||||
|
package com.ics.admin.pay.wx;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.servlet.ServletInputStream;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付回调工具类
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class WxPayCallbackUtil {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
WxPayConfig wxPayConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取回调数据
|
||||||
|
* @param request
|
||||||
|
* @param response
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Map<String, String> wxChatPayCallback(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
|
||||||
|
//获取报文
|
||||||
|
String body = getRequestBody(request);
|
||||||
|
|
||||||
|
//随机串
|
||||||
|
String nonceStr = request.getHeader("Wechatpay-Nonce");
|
||||||
|
|
||||||
|
//微信传递过来的签名
|
||||||
|
String signature = request.getHeader("Wechatpay-Signature");
|
||||||
|
|
||||||
|
//证书序列号(微信平台)
|
||||||
|
String serialNo = request.getHeader("Wechatpay-Serial");
|
||||||
|
|
||||||
|
//时间戳
|
||||||
|
String timestamp = request.getHeader("Wechatpay-Timestamp");
|
||||||
|
|
||||||
|
//构造签名串 应答时间戳\n,应答随机串\n,应答报文主体\n
|
||||||
|
String signStr = Stream.of(timestamp, nonceStr, body).collect(Collectors.joining("\n", "", "\n"));
|
||||||
|
|
||||||
|
Map<String, String> map = new HashMap<>(2);
|
||||||
|
try {
|
||||||
|
//验证签名是否通过
|
||||||
|
boolean result = verifiedSign(serialNo, signStr, signature);
|
||||||
|
if(result){
|
||||||
|
//解密数据
|
||||||
|
String plainBody = decryptBody(body);
|
||||||
|
return convertWechatPayMsgToMap(plainBody);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换body为map
|
||||||
|
* @param plainBody
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Map<String,String> convertWechatPayMsgToMap(String plainBody){
|
||||||
|
|
||||||
|
Map<String,String> paramsMap = new HashMap<>(2);
|
||||||
|
|
||||||
|
JSONObject jsonObject = JSONObject.parseObject(plainBody);
|
||||||
|
|
||||||
|
//商户订单号
|
||||||
|
paramsMap.put("out_trade_no",jsonObject.getString("out_trade_no"));
|
||||||
|
//交易状态
|
||||||
|
paramsMap.put("trade_state",jsonObject.getString("trade_state"));
|
||||||
|
// 微信订单号
|
||||||
|
paramsMap.put("transaction_id",jsonObject.getString("transaction_id"));
|
||||||
|
|
||||||
|
//附加数据
|
||||||
|
paramsMap.put("attach",jsonObject.getString("attach"));
|
||||||
|
if (jsonObject.getJSONObject("attach") != null && !jsonObject.getJSONObject("attach").equals("")){
|
||||||
|
paramsMap.put("account_no",jsonObject.getJSONObject("attach").getString("accountNo"));
|
||||||
|
}
|
||||||
|
return paramsMap;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解密body的密文
|
||||||
|
*
|
||||||
|
* "resource": {
|
||||||
|
* "original_type": "transaction",
|
||||||
|
* "algorithm": "AEAD_AES_256_GCM",
|
||||||
|
* "ciphertext": "",
|
||||||
|
* "associated_data": "",
|
||||||
|
* "nonce": ""
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @param body
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String decryptBody(String body) throws UnsupportedEncodingException, GeneralSecurityException {
|
||||||
|
AesUtil aesUtil = new AesUtil(wxPayConfig.getKey().getBytes("utf-8"));
|
||||||
|
JSONObject object = JSONObject.parseObject(body);
|
||||||
|
JSONObject resource = object.getJSONObject("resource");
|
||||||
|
String ciphertext = resource.getString("ciphertext");
|
||||||
|
String associatedData = resource.getString("associated_data");
|
||||||
|
String nonce = resource.getString("nonce");
|
||||||
|
return aesUtil.decryptToString(associatedData.getBytes("utf-8"),nonce.getBytes("utf-8"),ciphertext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证签名
|
||||||
|
*
|
||||||
|
* @param serialNo 微信平台-证书序列号
|
||||||
|
* @param signStr 自己组装的签名串
|
||||||
|
* @param signature 微信返回的签名
|
||||||
|
* @return
|
||||||
|
* @throws UnsupportedEncodingException
|
||||||
|
*/
|
||||||
|
public boolean verifiedSign(String serialNo, String signStr, String signature) throws UnsupportedEncodingException {
|
||||||
|
return wxPayConfig.getVerifier().verify(serialNo, signStr.getBytes("utf-8"), signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取请求数据流
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getRequestBody(HttpServletRequest request) {
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
try (ServletInputStream inputStream = request.getInputStream();
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||||
|
) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
sb.append(line);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
222
ics-admin/src/main/java/com/ics/admin/pay/wx/WxPayCommon.java
Normal file
222
ics-admin/src/main/java/com/ics/admin/pay/wx/WxPayCommon.java
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
package com.ics.admin.pay.wx;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.ics.admin.pay.wx.constant.WxApiConstants;
|
||||||
|
import com.ics.admin.pay.wx.dto.WxChatBasePayDto;
|
||||||
|
import com.ics.admin.pay.wx.dto.WxChatPayDto;
|
||||||
|
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
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.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.security.Signature;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class WxPayCommon {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
WxPayConfig wxPayConfig;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 封装基础通用请求数据
|
||||||
|
*
|
||||||
|
* @param basePayData 微信支付基础请求数据
|
||||||
|
* @return 封装后的map对象
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getBasePayParams(WxChatBasePayDto basePayData) {
|
||||||
|
Map<String, Object> paramsMap = new HashMap<>();
|
||||||
|
paramsMap.put("appid", wxPayConfig.getAppid());
|
||||||
|
paramsMap.put("mchid", wxPayConfig.getMchId());
|
||||||
|
// 如果商品名称过长则截取
|
||||||
|
String title = basePayData.getDescription().length() > 120 ? basePayData.getDescription().substring(0, 120) : basePayData.getDescription();
|
||||||
|
paramsMap.put("description", title);
|
||||||
|
paramsMap.put("out_trade_no", basePayData.getOrderId());
|
||||||
|
paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(basePayData.getNotify()));
|
||||||
|
Map<String, Integer> amountMap = new HashMap<>();
|
||||||
|
amountMap.put("total", basePayData.getPrice().multiply(new BigDecimal("100")).intValue());
|
||||||
|
paramsMap.put("amount", amountMap);
|
||||||
|
return paramsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求对象(Post请求)
|
||||||
|
*
|
||||||
|
* @param paramsMap 请求参数
|
||||||
|
* @return Post请求对象
|
||||||
|
*/
|
||||||
|
public HttpPost getHttpPost(String url, Map<String, Object> paramsMap) {
|
||||||
|
// 1.设置请求地址
|
||||||
|
HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(url));
|
||||||
|
|
||||||
|
// 2.设置请求数据
|
||||||
|
Gson gson = new Gson();
|
||||||
|
String jsonParams = gson.toJson(paramsMap);
|
||||||
|
|
||||||
|
// 3.设置请求信息
|
||||||
|
StringEntity entity = new StringEntity(jsonParams, "utf-8");
|
||||||
|
entity.setContentType("application/json");
|
||||||
|
httpPost.setEntity(entity);
|
||||||
|
httpPost.setHeader("Accept", "application/json");
|
||||||
|
return httpPost;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析响应数据
|
||||||
|
*
|
||||||
|
* @param response 发送请求成功后,返回的数据
|
||||||
|
* @return 微信返回的参数
|
||||||
|
*/
|
||||||
|
public HashMap<String, String> resolverResponse(CloseableHttpResponse response) {
|
||||||
|
try {
|
||||||
|
// 1.获取请求码
|
||||||
|
int statusCode = response.getStatusLine().getStatusCode();
|
||||||
|
// 2.获取返回值 String 格式
|
||||||
|
final String bodyAsString = EntityUtils.toString(response.getEntity());
|
||||||
|
|
||||||
|
Gson gson = new Gson();
|
||||||
|
if (statusCode == 200) {
|
||||||
|
// 3.如果请求成功则解析成Map对象返回
|
||||||
|
HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
|
||||||
|
return resultMap;
|
||||||
|
} else {
|
||||||
|
if (StringUtils.isNoneBlank(bodyAsString)) {
|
||||||
|
System.out.println("微信支付请求失败,提示信息:{}" + bodyAsString);
|
||||||
|
// 4.请求码显示失败,则尝试获取提示信息
|
||||||
|
HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
|
||||||
|
throw new RuntimeException(resultMap.get("message"));
|
||||||
|
}
|
||||||
|
System.out.println("微信支付请求失败,未查询到原因,提示信息:{}" + response.toString());
|
||||||
|
// 其他异常,微信也没有返回数据,这就需要具体排查了
|
||||||
|
throw new IOException("request failed");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new RuntimeException(e.getMessage());
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
response.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取签名
|
||||||
|
*
|
||||||
|
* @param nonceStr 随机数
|
||||||
|
* @param appId 微信公众号或者小程序等的appid
|
||||||
|
* @param prepay_id 预支付交易会话ID
|
||||||
|
* @param timestamp 时间戳 10位
|
||||||
|
* @return String 新签名
|
||||||
|
*/
|
||||||
|
String getSign(String nonceStr, String appId, String prepay_id, long timestamp) {
|
||||||
|
|
||||||
|
//从下往上依次生成
|
||||||
|
String message = buildMessage(appId, timestamp, nonceStr, prepay_id);
|
||||||
|
//签名
|
||||||
|
try {
|
||||||
|
return sign(message.getBytes("utf-8"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("签名异常,请检查参数或商户私钥");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String sign(byte[] message) {
|
||||||
|
try {
|
||||||
|
//签名方式
|
||||||
|
Signature sign = Signature.getInstance("SHA256withRSA");
|
||||||
|
//私钥,通过MyPrivateKey来获取,这是个静态类可以接调用方法 ,需要的是_key.pem文件的绝对路径配上文件名
|
||||||
|
sign.initSign(PemUtil.loadPrivateKey(new FileInputStream(wxPayConfig.getPrivateKeyPath())));
|
||||||
|
sign.update(message);
|
||||||
|
return Base64.getEncoder().encodeToString(sign.sign());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("签名异常,请检查参数或商户私钥");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按照前端签名文档规范进行排序,\n是换行
|
||||||
|
*
|
||||||
|
* @param nonceStr 随机数
|
||||||
|
* @param appId 微信公众号或者小程序等的appid
|
||||||
|
* @param prepay_id 预支付交易会话ID
|
||||||
|
* @param timestamp 时间戳 10位
|
||||||
|
* @return String 新签名
|
||||||
|
*/
|
||||||
|
String buildMessage(String appId, long timestamp, String nonceStr, String prepay_id) {
|
||||||
|
return appId + "\n"
|
||||||
|
+ timestamp + "\n"
|
||||||
|
+ nonceStr + "\n"
|
||||||
|
+ prepay_id + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建微信支付订单-jsapi方式
|
||||||
|
*
|
||||||
|
* @param basePayData 基础请求信息,商品标题、商家订单id、订单价格
|
||||||
|
* @return 微信支付二维码地址
|
||||||
|
*/
|
||||||
|
public String wxJsApiPay(WxChatBasePayDto basePayData) {
|
||||||
|
// 1.获取请求参数的Map格式
|
||||||
|
Map<String, Object> paramsMap = getBasePayParams(basePayData);
|
||||||
|
// 1.1 添加支付者信息
|
||||||
|
Map<String, String> payerMap = new HashMap();
|
||||||
|
payerMap.put("openid", basePayData.getOpenId());
|
||||||
|
paramsMap.put("payer", payerMap);
|
||||||
|
|
||||||
|
// 2.获取请求对象
|
||||||
|
HttpPost httpPost = getHttpPost(WxApiConstants.JSAPI_PAY, paramsMap);
|
||||||
|
|
||||||
|
// 3.完成签名并执行请求
|
||||||
|
CloseableHttpResponse response = null;
|
||||||
|
try {
|
||||||
|
response = wxPayConfig.getWxPayClient().execute(httpPost);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new RuntimeException("微信支付请求失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4.解析response对象
|
||||||
|
HashMap<String, String> resultMap = resolverResponse(response);
|
||||||
|
if (resultMap != null) {
|
||||||
|
// 【预支付交易会话标识】 预支付交易会话标识。用于后续接口调用中使用,该值有效期为2小时
|
||||||
|
return resultMap.get("prepay_id");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信用户调用微信支付
|
||||||
|
*/
|
||||||
|
public WxChatPayDto wxPayCall(String prepayId) {
|
||||||
|
WxChatPayDto wxChatPayDto = new WxChatPayDto();
|
||||||
|
wxChatPayDto.setAppid(wxPayConfig.getAppid());
|
||||||
|
wxChatPayDto.setTimeStamp(String.valueOf(System.currentTimeMillis() / 1000));
|
||||||
|
wxChatPayDto.setNonceStr(UUID.randomUUID().toString().replaceAll("-", ""));
|
||||||
|
wxChatPayDto.setPrepayId("prepay_id=" + prepayId);
|
||||||
|
wxChatPayDto.setSignType("RSA");
|
||||||
|
wxChatPayDto.setPaySign(getSign(wxChatPayDto.getNonceStr(), wxChatPayDto.getAppid(), wxChatPayDto.getPrepayId(), Long.parseLong(wxChatPayDto.getTimeStamp())));
|
||||||
|
|
||||||
|
return wxChatPayDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
134
ics-admin/src/main/java/com/ics/admin/pay/wx/WxPayConfig.java
Normal file
134
ics-admin/src/main/java/com/ics/admin/pay/wx/WxPayConfig.java
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package com.ics.admin.pay.wx;
|
||||||
|
|
||||||
|
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 lombok.Data;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.PropertySource;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@PropertySource("classpath:application.yml") //读取配置文件
|
||||||
|
@ConfigurationProperties(prefix = "weixin") //读取wxpay节点
|
||||||
|
@Data
|
||||||
|
/**
|
||||||
|
* 微信支付静态常量类
|
||||||
|
*/
|
||||||
|
public class WxPayConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商户号
|
||||||
|
*/
|
||||||
|
private String mchId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商户API证书序列号
|
||||||
|
*/
|
||||||
|
private String mchSerialNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商户私钥文件
|
||||||
|
*/
|
||||||
|
private String privateKeyPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APIv3密钥
|
||||||
|
*/
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APPID
|
||||||
|
*/
|
||||||
|
private String appid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信服务器地址
|
||||||
|
*/
|
||||||
|
private String domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收结果通知地址
|
||||||
|
*/
|
||||||
|
private String notifyDomain;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取商户的私钥文件
|
||||||
|
*
|
||||||
|
* @param filename 证书地址
|
||||||
|
* @return 私钥文件
|
||||||
|
*/
|
||||||
|
public PrivateKey getPrivateKey(String filename) {
|
||||||
|
try {
|
||||||
|
return PemUtil.loadPrivateKey(new FileInputStream(filename));
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
throw new RuntimeException("私钥文件不存在");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取签名验证器
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public Verifier getVerifier() {
|
||||||
|
// 获取商户私钥
|
||||||
|
final PrivateKey privateKey = getPrivateKey(privateKeyPath);
|
||||||
|
|
||||||
|
// 私钥签名对象
|
||||||
|
PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);
|
||||||
|
|
||||||
|
// 身份认证对象
|
||||||
|
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
|
||||||
|
|
||||||
|
// 获取证书管理器实例
|
||||||
|
CertificatesManager certificatesManager = CertificatesManager.getInstance();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 向证书管理器增加需要自动更新平台证书的商户信息
|
||||||
|
certificatesManager.putMerchant(mchId, wechatPay2Credentials, key.getBytes(StandardCharsets.UTF_8));
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return certificatesManager.getVerifier(mchId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new RuntimeException("获取签名验证器失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取微信支付的远程请求对象
|
||||||
|
* @return Http请求对象
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public CloseableHttpClient getWxPayClient() {
|
||||||
|
|
||||||
|
// 获取签名验证器
|
||||||
|
Verifier verifier = getVerifier();
|
||||||
|
|
||||||
|
// 获取商户私钥
|
||||||
|
final PrivateKey privateKey = getPrivateKey(privateKeyPath);
|
||||||
|
|
||||||
|
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, privateKey)
|
||||||
|
.withValidator(new WechatPay2Validator(verifier));
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.ics.admin.pay.wx.constant;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求地址
|
||||||
|
*/
|
||||||
|
public class WxApiConstants {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* jsApi下单
|
||||||
|
*/
|
||||||
|
public static final String JSAPI_PAY = "/v3/pay/transactions/jsapi";
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.ics.admin.pay.wx.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信回调地址
|
||||||
|
*/
|
||||||
|
public class WxNotifyConstants {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单支付通知
|
||||||
|
*/
|
||||||
|
public static final String CASE_PAY_NOTIFY = "/meeting/wxPay/orderCallBack";
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package com.ics.admin.pay.wx.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class WxChatBasePayDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商家订单号,对应 out_trade_no (32)
|
||||||
|
*/
|
||||||
|
private String orderId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单金额
|
||||||
|
*/
|
||||||
|
private BigDecimal price;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回调地址
|
||||||
|
*/
|
||||||
|
private String notify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付用户的openid
|
||||||
|
*/
|
||||||
|
private String openId;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
|||||||
|
package com.ics.admin.pay.wx.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 前端微信支付所需参数
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class WxChatPayDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 需要支付的小程序id
|
||||||
|
*/
|
||||||
|
private String appid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间戳(当前的时间)
|
||||||
|
*/
|
||||||
|
private String timeStamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机字符串,不长于32位。
|
||||||
|
*/
|
||||||
|
private String nonceStr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小程序下单接口返回的prepay_id参数值,提交格式如:prepay_id=***
|
||||||
|
*/
|
||||||
|
private String prepayId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名类型,默认为RSA,仅支持RSA。
|
||||||
|
*/
|
||||||
|
private String signType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名,使用字段appId、timeStamp、nonceStr、package计算得出的签名值
|
||||||
|
*/
|
||||||
|
private String paySign;
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
spring:
|
spring:
|
||||||
redis:
|
redis:
|
||||||
database: 5
|
database: 5
|
||||||
host: 127.0.0.1
|
host: 192.168.0.11
|
||||||
port: 6379
|
port: 6379
|
||||||
# password: hycxli#123
|
# password: hycxli#123
|
||||||
timeout: 6000ms # 连接超时时长(毫秒)
|
timeout: 6000ms # 连接超时时长(毫秒)
|
||||||
@ -17,9 +17,9 @@ spring:
|
|||||||
druid:
|
druid:
|
||||||
# 主库数据源
|
# 主库数据源
|
||||||
master:
|
master:
|
||||||
url: jdbc:mysql://192.168.0.50:3306/zz-meeting?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
url: jdbc:mysql://222.184.49.22:3306/dbd-meeting?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||||
username: root
|
username: root
|
||||||
password: boyuekeji2023
|
password: boyue1!Z
|
||||||
# 从库数据源
|
# 从库数据源
|
||||||
slave:
|
slave:
|
||||||
#从数据源开关/默认关闭
|
#从数据源开关/默认关闭
|
||||||
@ -124,3 +124,12 @@ mybatis-plus:
|
|||||||
configuration:
|
configuration:
|
||||||
default-enum-type-handler: com.ics.common.handlers.MybatisEnumTypeHandler
|
default-enum-type-handler: com.ics.common.handlers.MybatisEnumTypeHandler
|
||||||
|
|
||||||
|
weixin:
|
||||||
|
appid: wx5582a07c1fbbcf06 # appid
|
||||||
|
mch-serial-no: 5899B7D34B161F9E24EFFCD50E95245521CA40AB # 证书序列号
|
||||||
|
private-key-path: /Users/wangqichao/cert/dbd/apiclient_key.pem # 证书路径
|
||||||
|
mch-id: 1665472343 # 商户号
|
||||||
|
key: CHANGYANGKONGGUhenanjianandianzi # APIv3密钥
|
||||||
|
domain: https://api.mch.weixin.qq.com # 微信服务器地址
|
||||||
|
notify-domain: http://222.184.49.22:9227 # 回调,自己的回调地址
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ jwtp:
|
|||||||
## 拦截路径,默认是/**
|
## 拦截路径,默认是/**
|
||||||
path: /**
|
path: /**
|
||||||
## 排除拦截路径,默认无
|
## 排除拦截路径,默认无
|
||||||
exclude-path: /admin/login/slide, /business/login/slide, /login/slide,/user/check_code,/captcha/check,/captcha/get,/system/sms/send,/user/register/submit,/weixin/login,/meeting/roomItemByRoom/**,/social_user_login/login,/wx/**,/wx/login,/user/check_mobile,/user/send_mobile,/user/search_customer,/user/check_code,/user/register,/password/send_mobile,/password/forgot,/auth/login,/social_user_login/login
|
exclude-path: /admin/login/slide, //meeting/wxPay/**, /business/login/slide, /login/slide,/user/check_code,/captcha/check,/captcha/get,/system/sms/send,/user/register/submit,/weixin/login,/meeting/roomItemByRoom/**,/social_user_login/login,/wx/**,/wx/login,/user/check_mobile,/user/send_mobile,/user/search_customer,/user/check_code,/user/register,/password/send_mobile,/password/forgot,/auth/login,/social_user_login/login
|
||||||
## 单个用户最大token数,默认-1不限制
|
## 单个用户最大token数,默认-1不限制
|
||||||
max-token: 1
|
max-token: 1
|
||||||
## url自动对应权限方式,0 简易模式,1 RESTful模式
|
## url自动对应权限方式,0 简易模式,1 RESTful模式
|
||||||
|
Loading…
x
Reference in New Issue
Block a user