Commit 925c72e9 by yuanchao

20240315微信支付

parent e42fd409
...@@ -75,11 +75,11 @@ ...@@ -75,11 +75,11 @@
<version>5.7.16</version> <version>5.7.16</version>
</dependency> </dependency>
<dependency> <!-- <dependency>
<groupId>org.wso2.apache.httpcomponents</groupId> <groupId>org.wso2.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId> <artifactId>httpclient</artifactId>
<version>4.3.1.wso2v1</version> <version>4.3.1.wso2v1</version>
</dependency> </dependency>-->
<dependency> <dependency>
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>
...@@ -114,15 +114,46 @@ ...@@ -114,15 +114,46 @@
<version>6.6.14</version> <version>6.6.14</version>
</dependency> </dependency>
<!--微信支付-->
<!-- 微信支付V3 目前新版本-->
<!-- 微信支付 SDK -->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
<!-- <dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.12</version>
</dependency>-->
<dependency> <dependency>
<groupId>com.github.wechatpay-apiv3</groupId> <groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId> <artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.9</version> <version>0.4.7</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.72</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.24</version> <version>1.18.24</version>
......
package com.qianhe.system.config;
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.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
/**
* @author yc
* @version 1.0
* @className WechatPayConfig
* @date 2024/3/15 14:55
* @description
*/
@Component
@Data
@Slf4j
@ConfigurationProperties(prefix = "wx")
public class WechatPayConfig {
/**
* 应用编号
*/
private String appId;
/**
* 商户号
*/
private String mchId;
/**
* APIv3密钥
*/
private String apiV3Key;
/**
* 支付通知回调地址
*/
private String notifyUrl;
/**
* 退款回调地址
*/
private String refundNotifyUrl;
/**
* API 证书中的 key.pem
*/
private String keyPemPath;
/**
* 商户序列号
*/
private String serialNo;
/**
* 获取商户的私钥文件
*
* @param keyPemPath
* @return
*/
public PrivateKey getPrivateKey(String keyPemPath) {
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(keyPemPath);
if (inputStream == null) {
throw new RuntimeException("私钥文件不存在");
}
return PemUtil.loadPrivateKey(inputStream);
}
/**
* 获取证书管理器实例
*
* @return
*/
@Bean
public Verifier getVerifier() throws GeneralSecurityException, IOException, HttpCodeException, NotFoundException {
log.info("获取证书管理器实例");
//获取商户私钥
PrivateKey privateKey = getPrivateKey(keyPemPath);
//私钥签名对象
PrivateKeySigner privateKeySigner = new PrivateKeySigner(serialNo, privateKey);
//身份认证对象
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
// 使用定时更新的签名验证器,不需要传入证书
CertificatesManager certificatesManager = CertificatesManager.getInstance();
certificatesManager.putMerchant(mchId, wechatPay2Credentials, apiV3Key.getBytes(StandardCharsets.UTF_8));
return certificatesManager.getVerifier(mchId);
}
/**
* 获取支付http请求对象
*
* @param verifier
* @return
*/
@Bean(name = "wxPayClient")
public CloseableHttpClient getWxPayClient(Verifier verifier) {
//获取商户私钥
PrivateKey privateKey = getPrivateKey(keyPemPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, serialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
return builder.build();
}
}
\ No newline at end of file
package com.qianhe.system.controller;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.qianhe.system.config.WechatPayConfig;
import com.qianhe.system.utils.WechatPayValidator;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author yc
* @version 1.0
* @className CallbackController
* @date 2024/3/15 15:26
* @description
*/
@RestController
@Slf4j
@RequestMapping("/callback")
public class CallbackController {
@Resource
private WechatPayConfig wechatPayConfig;
@Resource
private Verifier verifier;
private final ReentrantLock lock = new ReentrantLock();
/**
* 支付回调处理
*
* @param request
* @param response
* @return
*/
@PostMapping("/payNotify")
public Map<String, String> payNotify(HttpServletRequest request, HttpServletResponse response) {
log.info("支付回调");
// 处理通知参数
Map<String, Object> bodyMap = getNotifyBody(request);
if (bodyMap == null) {
return falseMsg(response);
}
log.warn("=========== 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱 ===========");
if (lock.tryLock()) {
try {
// 解密resource中的通知数据
String resource = bodyMap.get("resource").toString();
Map<String, Object> resourceMap = WechatPayValidator.decryptFromResource(resource, wechatPayConfig.getApiV3Key(), 1);
String orderNo = resourceMap.get("out_trade_no").toString();
// String transactionId = resourceMap.get("transaction_id").toString();
// 更改状态 获取订单号 修改订单状态为已支付
// TODO 根据订单号,做幂等处理,并且在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
log.warn("=========== 根据订单号,做幂等处理 ===========");
} finally {
//要主动释放锁
lock.unlock();
}
}
//成功应答
return trueMsg(response);
}
/**
* 退款回调处理
*
* @param request
* @param response
* @return
*/
@PostMapping("/refundNotify")
public Map<String, String> refundNotify(HttpServletRequest request, HttpServletResponse response) {
log.info("退款回调");
// 处理通知参数
Map<String, Object> bodyMap = getNotifyBody(request);
if (bodyMap == null) {
return falseMsg(response);
}
log.warn("=========== 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱 ===========");
if (lock.tryLock()) {
try {
// 解密resource中的通知数据
String resource = bodyMap.get("resource").toString();
Map<String, Object> resourceMap = WechatPayValidator.decryptFromResource(resource, wechatPayConfig.getApiV3Key(), 2);
String orderNo = resourceMap.get("out_trade_no").toString();
// String transactionId = resourceMap.get("transaction_id").toString();
log.info("退款所有参数" + resourceMap);
// TODO 根据订单号,做幂等处理,并且在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
// 更改订单状态为已退款
log.warn("=========== 根据订单号,做幂等处理 ===========");
} finally {
//要主动释放锁
lock.unlock();
}
}
//成功应答
return trueMsg(response);
}
private Map<String, Object> getNotifyBody(HttpServletRequest request) {
//处理通知参数
String body = WechatPayValidator.readData(request);
log.info("退款回调参数:{}", body);
// 转换为Map
Map<String, Object> bodyMap = JSONObject.parseObject(body, new TypeReference<Map<String, Object>>() {
});
// 微信的通知ID(通知的唯一ID)
String notifyId = bodyMap.get("id").toString();
// 验证签名信息
WechatPayValidator wechatPayValidator
= new WechatPayValidator(verifier, notifyId, body);
if (!wechatPayValidator.validate(request)) {
log.error("通知验签失败");
return null;
}
log.info("通知验签成功");
return bodyMap;
}
private Map<String, String> falseMsg(HttpServletResponse response) {
Map<String, String> resMap = new HashMap<>(8);
//失败应答
response.setStatus(500);
resMap.put("code", "ERROR");
resMap.put("message", "通知验签失败");
return resMap;
}
private Map<String, String> trueMsg(HttpServletResponse response) {
Map<String, String> resMap = new HashMap<>(8);
//成功应答
response.setStatus(200);
resMap.put("code", "SUCCESS");
resMap.put("message", "成功");
return resMap;
}
}
\ No newline at end of file
package com.qianhe.system.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.qianhe.common.core.domain.AjaxResult;
import com.qianhe.system.config.WechatPayConfig;
import com.qianhe.system.domain.WaterGoods;
import com.qianhe.system.utils.WechatPayRequest;
import io.lettuce.core.dynamic.annotation.Param;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import static com.qianhe.common.core.domain.AjaxResult.success;
/**
* @author yc
* @version 1.0
* @className PayController
* @date 2024/3/15 15:03
* @description
*/
@Slf4j
@RestController
@RequestMapping("/pay")
public class PayController {
@Resource
private WechatPayConfig wechatPayConfig;
@Resource
private WechatPayRequest wechatPayRequest;
/**
* 预支付订单生成入口
*/
@GetMapping("/transactions")
public AjaxResult transactions(WaterGoods goods) {
// 统一参数封装
Map<String, Object> params = new HashMap<>(10);
// 1,appid:公众号或移动应用的唯一标识符。
params.put("appid", wechatPayConfig.getAppId());
// 2,mch_id:商户号,由微信支付分配。
params.put("mchid", wechatPayConfig.getMchId());
// 3.description body:商品描述。
params.put("description", "奥迪a4l 2023-限量款");
// 4.out_trade_no:商户订单号,由商户自定义。
params.put("out_trade_no", "we56f45waf4w6a5fwa");
// 5.notify_url:接收微信支付异步通知回调地址。
params.put("notify_url", wechatPayConfig.getNotifyUrl());
// 6.total_fee:订单总金额,单位为分。
Map<String, Object> amountMap = new HashMap<>(4);
// 金额单位为分
amountMap.put("total", 999999);
amountMap.put("currency", "CNY");
params.put("amount", amountMap);
// 7.openid:用户在商户appid下的唯一标识。
Map<String, Object> payerMap = new HashMap<>(4);
// openid 需要前端小程序通过用户code 请求微信接口获取用户唯一openid 不懂的看官方文档:https://developers.weixin.qq.com/doc/aispeech/miniprogram/quickuse.html
payerMap.put("openid", goods.getOpenId());
params.put("payer", payerMap);
String paramsStr = JSON.toJSONString(params);
log.info("请求参数 ===> {}" + paramsStr);
// 微信预支付下单接口路径
String payUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
// 获取支付 prepay_id参数
String resStr = wechatPayRequest.wechatHttpOrderPost(payUrl, paramsStr);
Map<String, Object> resMap = JSONObject.parseObject(resStr, new TypeReference<Map<String, Object>>() {
});
Object prepayId = resMap.get("prepay_id");
// 得到当前系统时间搓
String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
// 获取签名
String paySign;
try {
StringBuilder sb = new StringBuilder();
// 应用id
sb.append(wechatPayConfig.getAppId()).append("\n");
// 支付签名时间戳
sb.append(timeStamp).append("\n");
// 随机字符串
sb.append("5w7er7wa4fwa5e").append("\n");
// 预支付交易会话ID 这个要注意 key = "prepay_id=xxxxxx"
sb.append("prepay_id=").append(prepayId).append("\n");
// 签名
Signature sign = Signature.getInstance("SHA256withRSA");
// 获取商户私钥并进行签名
PrivateKey privateKey = wechatPayConfig.getPrivateKey(wechatPayConfig.getKeyPemPath());
sign.initSign(privateKey);
sign.update(sb.toString().getBytes(StandardCharsets.UTF_8));
// 得到签名
paySign = Base64.getEncoder().encodeToString(sign.sign());
} catch (Exception e) {
log.error("支付模块_生成交易签名失败!" + e);
return success(new HashMap<>());
}
// 将签名时数据和签名一起返回前端用于前端吊起支付
Map<String, Object> map = new HashMap<>();
// 小程序id
map.put("appId", wechatPayConfig.getAppId());
// 时间戳
map.put("timeStamp", timeStamp);
// 随机字符串
map.put("nonceStr", "56523268632356");
// 预支付交易会话ID
map.put("package", "prepay_id=" + prepayId);
// 签名方式
map.put("signType", "RSA");
// 签名
map.put("paySign", paySign);
System.out.println(map);
return success(map);
}
/**
* 申请退款
*/
@GetMapping("/refundOrder")
public String refundOrder() {
log.info("根据订单号申请退款,订单号: {}", "要退款的订单号 这里写死");
// 退款请求路径
String url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
// 设置参数
Map<String, Object> params = new HashMap<>(2);
// 要退款的订单编号订单编号
params.put("out_trade_no", "57984wera64");
// 商户自定义退款记录单号 用于退款记录的单号 跟退款订单号不是一样的
int outRefundNo = new Random().nextInt(999999999);
log.info("退款申请号:{}", outRefundNo);
params.put("out_refund_no", outRefundNo + "");
// 退款原因
params.put("reason", "申请退款");
// 退款通知回调地址
params.put("notify_url", wechatPayConfig.getRefundNotifyUrl());
Map<String, Object> amountMap = new HashMap<>();
//退款金额,单位:分
amountMap.put("refund", 999999);
//原订单金额,单位:分
amountMap.put("total", 99999);
//退款币种
amountMap.put("currency", "CNY");
params.put("amount", amountMap);
String paramsStr = JSON.toJSONString(params);
// todo 插入一条退款记录到数据库
log.info("请求参数 ===> {}" + paramsStr);
String res = wechatPayRequest.wechatHttpPost(url, paramsStr);
log.info("退款结果:{}", res);
return res;
}
}
\ No newline at end of file
...@@ -39,6 +39,8 @@ public class WaterGoodsCartController extends BaseController { ...@@ -39,6 +39,8 @@ public class WaterGoodsCartController extends BaseController {
* @param waterGoodsCart * @param waterGoodsCart
* @return * @return
*/ */
@GetMapping("/list") @GetMapping("/list")
public TableDataInfo list(WaterGoodsCart waterGoodsCart){ public TableDataInfo list(WaterGoodsCart waterGoodsCart){
//查询全部商品图片 //查询全部商品图片
......
package com.qianhe.system.controller;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import com.github.wxpay.sdk.WXPayUtil;
import com.qianhe.common.core.domain.AjaxResult;
import com.qianhe.system.domain.WaterGoodsCart;
import com.qianhe.system.domain.WeChatPay;
import com.qianhe.system.service.impl.WeChatPayService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import static com.qianhe.common.core.domain.AjaxResult.success;
/**
* @author yc
* @version 1.0
* @className WeChatPayController
* @date 2024/3/4 15:26
* @description 支付下单
*/
@RestController
@RequestMapping("/weChatPay")
public class WeChatPayController {
@Value("${wx.appId}")
private String appId;
@Value("${wx.mchId}")
private String mchId;
@Value("${wx.apiKey}")
private String apiKey;
@GetMapping("/pay")
public AjaxResult orderPay(@RequestBody WaterGoodsCart goods){
Map<String, Object> ResultMap = new HashMap<String, Object>();
try {
WeChatPay weChatPay = new WeChatPay();
weChatPay.setAppid(appId);//公众号appid
weChatPay.setMch_id(mchId);
weChatPay.setApi_key(apiKey);//api密钥
weChatPay.setNonce_str(WXPayUtil.generateNonceStr());// 32位随机字符串
weChatPay.setBody(goods.getGoodsName()+goods.getGoodsTypeName()+goods.getGoodsId());// 商品描述
weChatPay.setTotal_fee(String.valueOf(goods.getGoodsPrice())); //标价金额
weChatPay.setOut_trade_no(IdUtil.simpleUUID());// 商户订单号 唯一
weChatPay.setNotify_url("https://notify_url");//异步回调地址
weChatPay.setTrade_type("JSAPI"); // 交易类型 JSAPI--JSAPI支付(或小程序支付)、NATIVE--Native支付、APP--app支付,MWEB--H5支付
weChatPay.setAttach("附加数据NO.1");//附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
ResultMap = WeChatPayService.Unifiedorder(weChatPay);
//log.info("返回结果:{}",JSONUtil.toJsonStr(ResultMap));
} catch (Exception e) {
ResultMap.put("code", 2);
ResultMap.put("msg", "系统异常错误代码:" + e.getMessage());
e.printStackTrace();
}
return success(JSONUtil.toJsonStr(ResultMap));
}
}
\ No newline at end of file
...@@ -64,4 +64,6 @@ public class WaterGoods ...@@ -64,4 +64,6 @@ public class WaterGoods
/** 状态(1上架0下架) */ /** 状态(1上架0下架) */
@Excel(name = "状态", readConverterExp = "1=上架0下架") @Excel(name = "状态", readConverterExp = "1=上架0下架")
private Integer status; private Integer status;
private String openId;
} }
package com.qianhe.system.domain;
import lombok.Data;
/**
* @author yc
* @version 1.0
* @className WeChatPay
* @date 2024/3/4 13:42
* @description 微信支付预下单实体类
*/
@Data
public class WeChatPay {
//返回状态码 此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断
public String return_code;
//返回信息 当return_code为FAIL时返回信息为错误原因 ,例如 签名失败 参数格式校验错误
private String return_msg;
//公众账号ID 调用接口提交的公众账号ID
private String appid;
//商户号 调用接口提交的商户号
private String mch_id;
//api密钥 详见:https://pay.weixin.qq.com/index.php/extend/employee
private String api_key;
//设备号 自定义参数,可以为请求支付的终端设备号等
private String device_info;
//随机字符串 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 微信返回的随机字符串
private String nonce_str;
//签名 微信返回的签名值,详见签名算法:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3
private String sign;
//业务结果 SUCCESS SUCCESS/FAIL
private String result_code;
//错误代码 当result_code为FAIL时返回错误代码,详细参见下文错误列表
private String err_code;
//错误代码描述 当result_code为FAIL时返回错误描述,详细参见下文错误列表
private String err_code_des;
//交易类型 JSAPI JSAPI -JSAPI支付 NATIVE -Native支付 APP -APP支付 说明详见;https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
private String trade_type;
//预支付交易会话标识 微信生成的预支付会话标识,用于后续接口调用中使用,该值有效期为2小时
private String prepay_id;
//二维码链接 weixin://wxpay/bizpayurl/up?pr=NwY5Mz9&groupid=00 trade_type=NATIVE时有返回,此url用于生成支付二维码,然后提供给用户进行扫码支付。注意:code_url的值并非固定,使用时按照URL格式转成二维码即可
private String code_url;
//商品描述 商品简单描述,该字段请按照规范传递,具体请见 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
private String body;
//商家订单号 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|* 且在同一个商户号下唯一。详见商户订单号 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
private String out_trade_no;
//标价金额 订单总金额,单位为分,详见支付金额 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
private String total_fee;
//终端IP 支持IPV4和IPV6两种格式的IP地址。用户的客户端IP
private String spbill_create_ip;
//通知地址 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http
private String notify_url;
//子商户号 sub_mch_id 非必填(商户不需要传入,服务商模式才需要传入) 微信支付分配的子商户号
private String sub_mch_id;
//附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
private String attach;
//商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。
private String out_refund_no;
//退款总金额,单位为分,只能为整数,可部分退款。详见支付金额 https://pay.weixin.qq.com/wiki/doc/api/native_sl.php?chapter=4_2
private String refund_fee;
//退款原因 若商户传入,会在下发给用户的退款消息中体现退款原因 注意:若订单退款金额≤1元,且属于部分退款,则不会在退款消息中体现退款原因
private String refund_desc;
//交易结束时间 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则 注意:最短失效时间间隔必须大于5分钟
private String time_expire;
//用户标识 trade_type=JSAPI,此参数必传,用户在主商户appid下的唯一标识。openid和sub_openid可以选传其中之一,如果选择传sub_openid,则必须传sub_appid。下单前需要调用【网页授权获取用户信息: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html 】接口获取到用户的Openid。
private String openid;
}
\ No newline at end of file
package com.qianhe.system.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayUtil;
import com.qianhe.system.domain.WeChatPay;
import com.qianhe.system.utils.WeChatPayUrl;
import com.qianhe.system.utils.WxChatPayCommonUtil;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* @author yc
* @version 1.0
* @className WeChatPayService
* @date 2024/3/4 13:45
* @description 微信支付接口封装服务
*/
public class WeChatPayService {
private static final DecimalFormat df = new DecimalFormat("#");
/**
* 微信支付统一预下单接口 请查看接口规则 https://pay.weixin.qq.com/wiki/doc/api/native_sl.php?chapter=9_1
* @param weChatPay 参数值appid 商户id等等
* @return Map<String, Object> NATIVE支付则返回二维码扫描地址
* @throws Exception
*/
public static Map<String, Object> Unifiedorder(WeChatPay weChatPay) throws Exception {
Map<String, Object> ResultMap = new HashMap<String, Object>();
//todo 创建请求参数
SortedMap<String, String> req = new TreeMap<String, String>();
req.put("appid", weChatPay.getAppid()); //公众号
req.put("mch_id", weChatPay.getMch_id()); // 商户号
req.put("nonce_str", WXPayUtil.generateNonceStr()); // 32位随机字符串
req.put("body", weChatPay.getBody()); // 商品描述
req.put("out_trade_no", weChatPay.getOut_trade_no()); // 商户订单号
req.put("total_fee", df.format(Double.parseDouble(weChatPay.getTotal_fee()) * 100)); // 标价金额(分)
req.put("spbill_create_ip", weChatPay.getSpbill_create_ip()); // 终端IP
req.put("notify_url", weChatPay.getNotify_url()); // 回调地址
req.put("trade_type", weChatPay.getTrade_type()); // 交易类型
req.put("attach", weChatPay.getAttach()); // 签名
if (ObjectUtil.isNotEmpty(weChatPay.getSub_mch_id())) {
//todo 服务商模式
req.put("sub_mch_id", weChatPay.getSub_mch_id());//子商户号 微信支付 分配的子商户号
}
if (ObjectUtil.isNotEmpty(weChatPay.getTime_expire())) {
//todo 设置订单结束时间
//交易结束时间 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。
req.put("time_expire", weChatPay.getTime_expire());
}
if (ObjectUtil.isNotEmpty(weChatPay.getOpenid())) {
//todo JSAPI支付
req.put("openid", weChatPay.getOpenid());//用户标识 trade_type=JSAPI,此参数必传,用户在主商户appid下的唯一标识。openid和sub_openid可以选传其中之一,如果选择传sub_openid,则必须传sub_appid。下单前需要调用【网页授权获取用户信息: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html 】接口获取到用户的Openid。
}
req.put("sign", WXPayUtil.generateSignature(req, weChatPay.getApi_key(), WXPayConstants.SignType.MD5)); // 签名
//todo 生成要发送的 xml
String xmlBody = WXPayUtil.generateSignedXml(req, weChatPay.getApi_key());
System.err.println(String.format("微信支付预下单请求 xml 格式:\n%s", xmlBody));
//todo 发送 POST 请求 统一下单 API 并携带 xmlBody 内容,然后获得返回接口结果
String result = WxChatPayCommonUtil.httpsRequest(WeChatPayUrl.Uifiedorder, "POST", xmlBody);
System.err.println(String.format("%s", result));
//todo 将返回结果从 xml 格式转换为 map 格式
Map<String, String> WxResultMap = WXPayUtil.xmlToMap(result);
//todo 判断通信状态 此字段是通信标识,非交易标识
if (ObjectUtil.isNotEmpty(WxResultMap.get("return_code")) && WxResultMap.get("return_code").equals("SUCCESS")) {
//todo 业务结果
if (WxResultMap.get("result_code").equals("SUCCESS")) {
//todo 预下单成功
ResultMap.put("code", 0);
ResultMap.put("msg", "预下单成功");
//微信订单号
ResultMap.put("out_trade_no", weChatPay.getOut_trade_no());
switch (WxResultMap.get("trade_type")) {
case "NATIVE":
//二维码地址
ResultMap.put("QrCode", WxResultMap.get("code_url"));
break;
case "MWEB":
//二维码地址
ResultMap.put("mweb_url", WxResultMap.get("mweb_url"));
break;
case "JSAPI":
//预支付交易会话标识 微信生成的预支付回话标识,用于后续接口调用中使用,该值有效期为2小时
ResultMap.put("prepay_id", WxResultMap.get("prepay_id"));
break;
}
} else {
//todo 下单失败
ResultMap.put("code", 2);
ResultMap.put("msg", WxResultMap.get("err_code_des"));
}
} else {
//todo 通信异常
ResultMap.put("code", 2);
ResultMap.put("msg", WxResultMap.get("return_msg"));//当return_code为FAIL时返回信息为错误原因 ,例如 签名失败 参数格式校验错误
}
return ResultMap;
}
}
\ No newline at end of file
package com.qianhe.system.utils;
/**
* @author yc
* @version 1.0
* @className WeChatPayUrl
* @date 2024/3/4 10:17
* @description 微信支付接口Url列表
*/
public class WeChatPayUrl {
//统一下单预下单接口url
public static final String Uifiedorder = "https://api.mch.weixin.qq.com/pay/unifiedorder";
//订单状态查询接口URL
public static final String Orderquery = "https://api.mch.weixin.qq.com/pay/orderquery";
//订单申请退款
public static final String Refund = "https://api.mch.weixin.qq.com/secapi/pay/refund";
//付款码 支付
public static final String MicroPay = "https://api.mch.weixin.qq.com/pay/micropay";
//微信网页授权 获取“code”请求地址
public static final String GainCodeUrl = "https://open.weixin.qq.com/connect/oauth2/authorize";
//微信网页授权 获取“code” 回调地址
public static final String GainCodeRedirect_uri = "http://i5jmxe.natappfree.cc/boss/WeChatPayMobile/SkipPage.html";
}
\ No newline at end of file
package com.qianhe.system.utils;
import com.alibaba.fastjson.JSONObject;
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.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @author yc
* @version 1.0
* @className WechatPayRequest
* @date 2024/3/15 14:56
* @description
*/
@Component
@Slf4j
public class WechatPayRequest {
@Resource
private CloseableHttpClient wxPayClient;
/**
* 支付请求
*
* @param url
* @param paramsStr
* @return
*/
public String wechatHttpOrderPost(String url, String paramsStr) {
try {
HttpPost httpPost = new HttpPost(url);
StringEntity stringEntity = new StringEntity(paramsStr, "utf-8");
stringEntity.setContentType("application/json");
httpPost.setEntity(stringEntity);
httpPost.setHeader("Accept", "application/json");
CloseableHttpResponse response = wxPayClient.execute(httpPost);
//响应体
HttpEntity entity = response.getEntity();
String body = entity == null ? "" : EntityUtils.toString(entity);
//响应状态码
int statusCode = response.getStatusLine().getStatusCode();
//处理成功,204是,关闭订单时微信返回的正常状态码
if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) {
log.info("成功, 返回结果 = " + body);
} else {
String msg = "微信支付请求失败,响应码 = " + statusCode + ",返回结果 = " + body;
log.info("支付模块-生成订单 = " + msg);
throw new RuntimeException(msg);
}
return body;
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
/**
* 退款请求
*
* @param url
* @param paramsStr
* @return
*/
public String wechatHttpPost(String url, String paramsStr) {
try {
HttpPost httpPost = new HttpPost(url);
StringEntity stringEntity = new StringEntity(paramsStr, "utf-8");
stringEntity.setContentType("application/json");
httpPost.setEntity(stringEntity);
httpPost.setHeader("Accept", "application/json");
CloseableHttpResponse response = wxPayClient.execute(httpPost);
//响应体
HttpEntity entity = response.getEntity();
String body = entity == null ? "" : EntityUtils.toString(entity);
//响应状态码
int statusCode = response.getStatusLine().getStatusCode();
//处理成功,204是,关闭订单时微信返回的正常状态码
if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) {
log.info("成功, 返回结果 = " + body);
// 请求成功或已处理成功,返回成功的响应
return "退款处理中";
} else if (statusCode == HttpStatus.SC_BAD_REQUEST || statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR) {
// 请求参数错误或系统错误,返回失败的响应
JSONObject json = JSONObject.parseObject(body);
return json.getString("message");
} else if (statusCode == HttpStatus.SC_FORBIDDEN) {
// 权限问题,没有退款权限
return "没有退款权限";
} else if (statusCode == HttpStatus.SC_NOT_FOUND) {
// 订单号不存在
return "订单号不存在";
} else if (statusCode == 429) {
// 频率限制
return "退款请求频率过高,请稍后重试";
} else if (statusCode == HttpStatus.SC_PAYMENT_REQUIRED) {
// 余额不足
return "商户余额不足,请充值后重试";
} else {
// 其他状态码,返回通用的失败响应
return "退款失败,请稍后重试";
}
} catch (Exception e) {
log.info("支付模块-退款失败 = " + e.getMessage());
JSONObject json = JSONObject.parseObject(e.getMessage());
return json.getString("message");
}
}
}
\ No newline at end of file
package com.qianhe.system.utils;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
/**
* @author yc
* @version 1.0
* @className WechatPayValidator
* @date 2024/3/15 14:58
* @description
*/
@Slf4j
public class WechatPayValidator {
/**
* 应答超时时间,单位为分钟
*/
private static final long RESPONSE_EXPIRED_MINUTES = 5;
private final Verifier verifier;
private final String requestId;
private final String body;
public WechatPayValidator(Verifier verifier, String requestId, String body) {
this.verifier = verifier;
this.requestId = requestId;
this.body = body;
}
protected static IllegalArgumentException parameterError(String message, Object... args) {
message = String.format(message, args);
return new IllegalArgumentException("parameter error: " + message);
}
protected static IllegalArgumentException verifyFail(String message, Object... args) {
message = String.format(message, args);
return new IllegalArgumentException("signature verify fail: " + message);
}
public final boolean validate(HttpServletRequest request) {
try {
//处理请求参数
validateParameters(request);
//构造验签名串
String message = buildMessage(request);
String serial = request.getHeader(com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.WECHAT_PAY_SERIAL);
String signature = request.getHeader(com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.WECHAT_PAY_SIGNATURE);
//验签
if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",
serial, message, signature, requestId);
}
} catch (IllegalArgumentException e) {
log.warn(e.getMessage());
return false;
}
return true;
}
private void validateParameters(HttpServletRequest request) {
// NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
String[] headers = {com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.WECHAT_PAY_SERIAL,
com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.WECHAT_PAY_SIGNATURE,
com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.WECHAT_PAY_NONCE,
com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.WECHAT_PAY_TIMESTAMP};
String header = null;
for (String headerName : headers) {
header = request.getHeader(headerName);
if (header == null) {
throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
}
}
//判断请求是否过期
String timestampStr = header;
try {
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
// 拒绝过期请求
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);
}
} catch (DateTimeException | NumberFormatException e) {
throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);
}
}
private String buildMessage(HttpServletRequest request) {
String timestamp = request.getHeader(com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.WECHAT_PAY_TIMESTAMP);
String nonce = request.getHeader(com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.WECHAT_PAY_NONCE);
return timestamp + "\n"
+ nonce + "\n"
+ body + "\n";
}
private String getResponseBody(CloseableHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";
}
/**
* 对称解密,异步通知的加密数据
*
* @param resource 加密数据
* @param apiV3Key apiV3密钥
* @param type 1-支付,2-退款
* @return
*/
public static Map<String, Object> decryptFromResource(String resource, String apiV3Key, Integer type) {
String msg = type == 1 ? "支付成功" : "退款成功";
log.info(msg + ",回调通知,密文解密");
try {
//通知数据
Map<String, String> resourceMap = JSONObject.parseObject(resource, new TypeReference<Map<String, String>>() {
});
//数据密文
String ciphertext = resourceMap.get("ciphertext");
//随机串
String nonce = resourceMap.get("nonce");
//附加数据
String associatedData = resourceMap.get("associated_data");
log.info("密文: {}", ciphertext);
AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
String resourceStr = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
ciphertext);
log.info(msg + ",回调通知,解密结果 : {}", resourceStr);
return JSONObject.parseObject(resourceStr, new TypeReference<Map<String, Object>>() {
});
} catch (Exception e) {
throw new RuntimeException("回调参数,解密失败!");
}
}
/**
* 将通知参数转化为字符串
*
* @param request
* @return
*/
public static String readData(HttpServletRequest request) {
BufferedReader br = null;
try {
StringBuilder result = new StringBuilder();
br = request.getReader();
for (String line; (line = br.readLine()) != null; ) {
if (result.length() > 0) {
result.append("\n");
}
result.append(line);
}
return result.toString();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
\ No newline at end of file
package com.qianhe.system.utils;
import com.qianhe.common.utils.StringUtils;
import javax.net.ssl.HttpsURLConnection;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.URL;
/**
* @author yc
* @version 1.0
* @className WxChatPayCommonUtil
* @date 2024/3/4 10:17
* @description 自定义微信支付工具类
*/
public class WxChatPayCommonUtil {
/**
* 发送 http 请求
* @param requestUrl 请求路径
* @param requestMethod 请求方式(GET/POST/PUT/DELETE/...)
* @param outputStr 请求参数体
* @return 结果信息
*/
public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
try {
URL url = new URL(requestUrl);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
// 设置请求方式(GET/POST)
conn.setRequestMethod(requestMethod);
conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
// 当outputStr不为null时向输出流写数据
if (null != outputStr) {
OutputStream outputStream = conn.getOutputStream();
// 注意编码格式
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 从输入流读取返回内容
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
// 释放资源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
inputStream = null;
conn.disconnect();
return buffer.toString();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 获取ip
* @param request 请求
* @return ip 地址
*/
public static String getIp(HttpServletRequest request) {
if (request == null) {
return "";
}
String ip = request.getHeader("X-Requested-For");
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
/**
* 从流中读取微信返回的xml数据
* @param httpServletRequest
* @return
* @throws IOException
*/
public static String readXmlFromStream(HttpServletRequest httpServletRequest) throws IOException, IOException {
InputStream inputStream = httpServletRequest.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
final StringBuffer sb = new StringBuffer();
String line = null;
try {
while ((line = bufferedReader.readLine()) != null) {
sb.append(line);
}
} finally {
bufferedReader.close();
inputStream.close();
}
return sb.toString();
}
/**
* 设置返回给微信服务器的xml信息
* @param returnCode
* @param returnMsg
* @return
*/
public static String setReturnXml(String returnCode, String returnMsg) {
return "<xml><return_code><![CDATA[" + returnCode + "]]></return_code><return_msg><![CDATA[" + returnMsg + "]]></return_msg></xml>";
}
}
\ No newline at end of file
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC+vQS+6VGcsX6i
Au5HhEXviSfTuI+F3ZHGD/9m/3sPNxIsiA/46E9vk0+Ulxmaxr5x6KNbyko/XVmQ
MN1vxFO/rGpqk24shDWZpxyMEwMxvw4EcbhbGmzRoxmrwUbujmJznIq6JcqqaBHa
Z9LwHzVWtOZsRwHVr6KivRZr27bN3aEv26nnMTDkg1tkwi0AKG4EmxlYU5zjnFXh
3TDkOZYZsq24bXllSCXlTCCWpBgPbNyBzAshT3M9nD4GeW+sqrnsVMEbaSxjd6xg
uB28OCxyOb1BJKXEZYQJBmQ+ik8GoNQtx/00tE1Y5GPH+QONs1sYlQhu4FFTtvvS
069DIOfjAgMBAAECggEBAKg8NkxhpS9tSwGBTkRcQgdGVY+kMUtkpCgrgh2J6DQC
YhBPLq9f0HjcWQv5vobLF72G8VeL9LMxFkddImNrqmbcn7xDL6EqN9DAGijeuCmP
l8CJwY7xntvFXWYmAvd1NRc+EwqfPMPTKTQX8XEERdqlkrwcYVzmHrAl0fnugK3Q
btsO931xHGJ98CSVOMURiRmTdjKAahXYIlWfU0Xdqb+16uIPD31tLC17Kf5+Odpd
Rv9rxLBrp6FxuA+fwkKT+f3bNxLOQfmsb+0nuE/8lNe3VslV96K+CUB7MELQ3wx3
hQ5BgVa3JNpOlY5TMjyP+Yi+OEA+KJ/xPMKsieP6fFECgYEA+FxERDwoAWjn5V5x
7yVAJ4Vnd5WaIxIU6J8YDjDX5CEzXsICoubxioAPbZoC93T/6h8WGWvm3s8FfIlp
dWRIAK/YA4fqAwcEyTuk6p2upU1IAyjz09n+OshIvBZ+j/b05xFk5nMM616HZrAn
mIB1isDn8UhKPKjhx2JatOvdBqsCgYEAxJsAoizmYHYeY0aW+HX2h9LqImxCuPmK
izaMZCxq9RBGwGT/UyHXo3CBr+SILvjAYtKDi5danIygiVp76yBWBQQmKh5f0z7m
FoPRwmf7o1qMV+KuFdydLuinA5864aONOEeoQOBuWP6fJ11+37ezVTsUyuC+fjhX
1+uNnhNqg6kCgYBjXxd5bdBb8AuI/Kb9lpv6tCfX8yW/DocLJEzNsMFQ5+/T5DCF
2X2feumxYsP5GvkiRdnjxgaT86UwVRK7A6rDi5gUoZcCKxbBJXow3XJ5dVhw2zvj
8f8EqgpgJ2fwVlFa+tyyUCpFKodmkOjm3c8p+1FSeygo6TGdQz8j0JOZDQKBgQCx
+2RybFVOZAAUfXX0jc3VneGpsfohPH7okkQ7914IZmi2iXf/CTcO1a4BztBePYDk
tZCykR1NdZ1rWsetzsMwnVXzcTXsphdjsIf3B21tr243rZVNYz/ElIeFpuOGGyqg
FXbC7KnY1QC9gU13N/UYJnknRJgO6fDUHoFA5nDZqQKBgADTGuWavhnw1SCRYZhz
QtonO5hXjNjwZHcmEaEneI3+9X64TQgXY1hQe3WWLUwDNyAhlsT8y1Jl1kgL0tJP
mxz0fsu47RdFR1rX+3ZIF5jNaxR6UIN2GQLlhvB7PCl7znKpph/nVthTO681Vx3V
mP/wQpwy8cWtmbyl7lcvVRN5
-----END PRIVATE KEY-----
...@@ -9,7 +9,7 @@ ruoyi: ...@@ -9,7 +9,7 @@ ruoyi:
# 实例演示开关 # 实例演示开关
demoEnabled: true demoEnabled: true
# 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
profile: D:/ruoyi/uploadPath profile: C:/ruoyi/uploadPath
# 获取ip地址开关 # 获取ip地址开关
addressEnabled: false addressEnabled: false
# 验证码类型 math 数组计算 char 字符验证 # 验证码类型 math 数组计算 char 字符验证
...@@ -71,13 +71,13 @@ spring: ...@@ -71,13 +71,13 @@ spring:
# redis 配置 # redis 配置
redis: redis:
# 地址 # 地址
host: 1.116.38.25 host: 127.0.0.1
# 端口,默认为6379 # 端口,默认为6379
port: 7789 port: 6379
# 数据库索引 # 数据库索引
database: 2 database: 2
# 密码 # 密码
password: qianheRedis2021 password:
# 连接超时时间 # 连接超时时间
timeout: 10s timeout: 10s
lettuce: lettuce:
...@@ -108,7 +108,9 @@ mybatis: ...@@ -108,7 +108,9 @@ mybatis:
mapperLocations: classpath*:mapper/**/*Mapper.xml mapperLocations: classpath*:mapper/**/*Mapper.xml
# 加载全局的配置文件 # 加载全局的配置文件
configLocation: classpath:mybatis/mybatis-config.xml configLocation: classpath:mybatis/mybatis-config.xml
#mybatis-plus:
# configuration:
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# PageHelper分页插件 # PageHelper分页插件
pagehelper: pagehelper:
helperDialect: mysql helperDialect: mysql
...@@ -138,21 +140,25 @@ wx: ...@@ -138,21 +140,25 @@ wx:
# appSecret: bd486fd54bd1ea5e9b198911d765ce6a # appSecret: bd486fd54bd1ea5e9b198911d765ce6a
# access-token-uri: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${wx.appId}&secret=${wx.appSecret} # access-token-uri: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${wx.appId}&secret=${wx.appSecret}
#正式 #正式
#微信小程序qppid #微信小程序qppid @126.com
appId: wx3c0181d9800dfbf2 appId: wx3c0181d9800dfbf2
#小程序密钥 #小程序密钥
appSecret: d1382ba5e014038ee38eda00d737ac43 appSecret: d1382ba5e014038ee38eda00d737ac43
access-token-uri: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${sswx.appId}&secret=${sswx.appSecret} access-token-uri: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${wx.appId}&secret=${wx.appSecret}
#商户号 #商户号
mchId: xxx mchId: 1668408731
#证书序列号 #证书序列号
mch-serial-no: xxx serialNo: 2562AB35D9BFE5CB875FA73954B6F51421E0F364
#api密钥 #api密钥
api-key: xxx apiV3Key: 2562AB35D9BFE5CB875FA73954B6F5WW #微信支付v3密钥
#回调接口地址 #apiKey: asdkjfhakjsdhf12321349898aksjhdj #微信支付v2密钥
notify-url: xxx #支付通知回调
notifyUrl: http://localhost:5125/callback/payNotify
#退款通知回调
refundNotifyUrl: http://localhost:5125/callback/refundNotify
#证书地址 #证书地址
key-path: xxx keyPemPath: apiclient_key.pem
# 送水端小程序 # 送水端小程序
sswx: sswx:
...@@ -160,7 +166,7 @@ sswx: ...@@ -160,7 +166,7 @@ sswx:
# appId: wxc89600b5b0aee68d # appId: wxc89600b5b0aee68d
# appSecret: 27ebe3778435c719cc1b97f260b7e026 # appSecret: 27ebe3778435c719cc1b97f260b7e026
# access-token-uri: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${sswx.appId}&secret=${sswx.appSecret} # access-token-uri: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${sswx.appId}&secret=${sswx.appSecret}
#正式 #正式 @163.com
appId: wx75635671bf9fe9bb appId: wx75635671bf9fe9bb
appSecret: 5ac4f25af14d7cfb3e62870fa1719459 appSecret: 5ac4f25af14d7cfb3e62870fa1719459
access-token-uri: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${wx.appId}&secret=${wx.appSecret} access-token-uri: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${sswx.appId}&secret=${sswx.appSecret}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment