jeecg-boot-master\jeecg-boot\jeecg-boot-module-mp\src\main\java\org\jeecg\modules\mp\cost\controller\MpPaymentController.java
package org.jeecg.modules.mp.cost.controller;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
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.util.PemUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import okhttp3.HttpUrl;
import org.apache.http.client.HttpClient;
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.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.aspectj.lang.annotation.Before;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.config.GlobalKeys;
import org.jeecg.modules.mp.util.WxPayUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
/**
* <p>
* 小程序微信支付
* </p>
*
* @Author DaQiang
* @since 2021-3-15
*/
@Api(tags = "小程序微信支付")
@Slf4j
@RestController
@RequestMapping("/cost/payment")
public class MpPaymentController {
/**
* 系统业务参数配置类
*/
@Autowired
private GlobalKeys globalKeys;
/**
* AppID(小程序ID)
*/
String appId;
/**
* 商户号
*/
String mchId;
/**
* 商户证书序列号
*/
String mchSerialNo;
/**
* APIv3密钥
*/
String apiV3Key;
/**
* 获取发起请求时的系统当前时间戳,即格林威治时间1970年01月01日00时00分00秒
* (北京时间1970年01月01日08时00分00秒)起至现在的总秒数,作为请求时间戳。
* 微信支付会拒绝处理很久之前发起的请求,请商户保持自身系统的时间准确。
*/
long timeStamp;
/**
* 请求随机串
*/
String nonceStr;
/**
* 商户私钥
*/
PrivateKey merchantPrivateKey;
/**
* 平台证书
*/
AutoUpdateCertificatesVerifier verifier;
/**
* HttpClient
*/
HttpClient httpClient;
/**
* HttpUrl
*/
HttpUrl httpurl;
/**
* 构造器
*
* @throws IOException
*/
public MpPaymentController(GlobalKeys globalKeys) throws IOException {
// 系统业务参数配置类
this.globalKeys = globalKeys;
// AppID(小程序ID)
this.appId = this.globalKeys.getAppId();
// 商户号
this.mchId = this.globalKeys.getMchId();
// 商户证书序列号
this.mchSerialNo = this.globalKeys.getMchSerialNo();
// APIv3密钥
this.apiV3Key = this.globalKeys.getApiV3Key();
// 系统当前时间戳
this.timeStamp = System.currentTimeMillis() / 1000;
// 请求随机串
this.nonceStr = WxPayUtil.createNonceStr();
// 初始化HttpUrl
this.httpurl = HttpUrl.parse("https://api.mch.weixin.qq.com/v3/certificates");
// 创建加载商户私钥、加载平台证书、初始化httpClient的通用方法
this.setup();
}
/**
* 获取私钥。
*
* @param filename 私钥文件路径 (required)
* @return 私钥对象
*/
public static PrivateKey getPrivateKey(String filename) throws IOException {
String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
try {
String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA", e);
} catch (InvalidKeySpecException e) {
throw new RuntimeException("无效的密钥格式");
}
}
/**
* 创建加载商户私钥、加载平台证书、初始化httpClient的通用方法
*
* @throws IOException
*/
@Before("")
public void setup() throws IOException {
// 加载商户私钥(privateKey:私钥字符串)
// merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
// 加载商户私钥
merchantPrivateKey = getPrivateKey("apiclient_key.pem");
// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3秘钥)
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();
}
/**
* 小程序支付统一下单
*
* @throws Exception
*/
@AutoLog(value = "小程序支付统一下单")
@ApiOperation(value = "小程序支付统一下单", notes = "小程序支付统一下单")
@PostMapping(value = "/createOrder")
public Result<JSONObject> createOrder(@RequestBody Map paramMap) throws IOException {
Result<JSONObject> result = new Result<JSONObject>();
JSONObject obj = new JSONObject();
Map<String, Object> map = new HashMap<>();
// 请求URL
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
// total:订单总金额,单位为分
Integer totalFee = (Integer) paramMap.get("totalFee");
// description:商品描述
String description = (String) paramMap.get("description");
// notify_url:支付回调通知URL,该地址必须为直接可访问的URL,不允许携带查询串。
String notifyUrl = (String) paramMap.get("notifyUrl");
// 用户唯一标识OpenID
// openid是微信用户在appid下的唯一用户标识(appid不同,则获取到的openid就不同),可用于永久标记一个用户。
String openid = (String) paramMap.get("openid");
// out_trade_no:商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一。
String outTradeNo = WxPayUtil.createOutTradeNo();
// 请求body参数
String reqdata = "{"
+ "\"amount\": {"
+ "\"total\": " + totalFee + ","
+ "\"currency\": \"CNY\""
+ "},"
+ "\"mchid\": \"" + mchId + "\","
+ "\"description\": \"" + description + "\","
+ "\"notify_url\": \"" + notifyUrl + "\","
+ "\"payer\": {"
+ "\"openid\": \"" + openid + "\"" + "},"
+ "\"out_trade_no\": \"" + outTradeNo + "\","
+ "\"goods_tag\": \"用水户水费\","
+ "\"appid\": \"" + appId + "\"" + "}";
StringEntity entity = new StringEntity(reqdata);
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
// 完成签名并执行请求
CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpPost);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
// 预支付交易会话标识(prepay_id)
// 统一下单接口返回的prepay_id参数值,提交格式如:prepay_id=***
String prepayIdEntity = EntityUtils.toString(response.getEntity());
JSONObject jsonResult = JSONObject.parseObject(prepayIdEntity);
String prepayId = (String) jsonResult.get("prepay_id");
// 签名生成
obj = getToken("GET", httpurl, "prepay_id=" + prepayId);
} else if (statusCode == 204) {
System.out.println("success");
} else {
System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity()));
throw new IOException("request failed");
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} finally {
response.close();
}
result.setResult(obj);
result.setSuccess(true);
result.setCode(200);
return result;
}
/**
* 签名生成
*
* @param method
* @param url
* @param body
* @return
*/
JSONObject getToken(String method, HttpUrl url, String body) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
// 返回JSON对象
JSONObject obj = new JSONObject();
// AppID(小程序ID)
obj.put("appId", appId);
// 商户号
obj.put("mchId", mchId);
// 该接口V3版本仅支持RSA
obj.put("signType", "RSA");
// 统一下单接口返回的prepay_id参数值,提交格式如:prepay_id=***。
obj.put("package", body);
// 发起请求时的系统当前时间戳
obj.put("timeStamp", String.valueOf(timeStamp));
// 请求随机串
obj.put("nonceStr", nonceStr);
// 构造签名串
String message = buildMessage(method, url, body);
// 签名
String signature = sign(message.getBytes("utf-8"));
/**signature = "mchid=\"" + mchId + "\","
+ "nonce_str=\"" + nonceStr + "\","
+ "timestamp=\"" + timestamp + "\","
+ "serial_no=\"" + mchSerialNo + "\","
+ "signature=\"" + signature + "\"";*/
obj.put("paySign", signature);
return obj;
}
/**
* 签名
*
* @param message
* @return
* @throws UnsupportedEncodingException
* @throws InvalidKeyException
* @throws SignatureException
* @throws NoSuchAlgorithmException
*/
String sign(byte[] message) throws UnsupportedEncodingException, InvalidKeyException, SignatureException, NoSuchAlgorithmException {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(merchantPrivateKey);
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
}
/**
* 构造签名串
*
* @param method
* @param url
* @param body
* @return
*/
String buildMessage(String method, HttpUrl url, String body) {
String canonicalUrl = url.encodedPath();
if (url.encodedQuery() != null) {
canonicalUrl += "?" + url.encodedQuery();
}
// method + "\n" + canonicalUrl + "\n" +
return appId + "\n"
+ timeStamp + "\n"
+ nonceStr + "\n"
+ body + "\n";
}
/**
* 查询订单
*
* @throws Exception
*/
@AutoLog(value = "查询订单")
@ApiOperation(value = "查询订单", notes = "查询订单")
@GetMapping(value = "/queryOrder")
public void QueryOrder() throws Exception {
// 请求URL
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/id/4200000745202011093730578574");
uriBuilder.setParameter("mchid", mchId);
// 完成签名并执行请求
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpGet);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
} else if (statusCode == 204) {
System.out.println("success");
} else {
System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity()));
throw new IOException("request failed");
}
} finally {
response.close();
}
}
/**
* 关闭订单
*
* @throws Exception
*/
@AutoLog(value = "关闭订单")
@ApiOperation(value = "关闭订单", notes = "关闭订单")
@GetMapping(value = "/closeOrder")
public void CloseOrder() throws Exception {
//请求URL
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/sdkphp12345678920201028112429/close");
//请求body参数
String reqdata = "{\"mchid\": \"" + mchId + "\"}";
StringEntity entity = new StringEntity(reqdata);
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpPost);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
} else if (statusCode == 204) {
System.out.println("success");
} else {
System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity()));
throw new IOException("request failed");
}
} finally {
response.close();
}
}
/**
* 申请交易账单
*
* @throws Exception
*/
@AutoLog(value = "申请交易账单")
@ApiOperation(value = "申请交易账单", notes = "申请交易账单")
@GetMapping(value = "/tradeBill")
public void TradeBill() throws Exception {
//请求URL
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/bill/tradebill");
uriBuilder.setParameter("bill_date", "2020-11-09");
uriBuilder.setParameter("bill_type", "ALL");
//完成签名并执行请求
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpGet);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
} else if (statusCode == 204) {
System.out.println("success");
} else {
System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity()));
throw new IOException("request failed");
}
} finally {
response.close();
}
}
/**
* 下载账单
*
* @param download_url
* @throws Exception
*/
@AutoLog(value = "下载账单")
@ApiOperation(value = "下载账单", notes = "下载账单")
@GetMapping(value = "/downloadUrl")
public void DownloadUrl(String download_url) throws Exception {
PrivateKey privateKey = getPrivateKey("privateKey");
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.toString().getBytes("utf-8")));
//初始化httpClient
//该接口无需进行签名验证、通过withValidator((response) -> true)实现
httpClient = WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, merchantPrivateKey).withValidator((response) -> true).build();
//请求URL
//账单文件的下载地址的有效时间为30s
URIBuilder uriBuilder = new URIBuilder(download_url);
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
//执行请求
CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpGet);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
} else if (statusCode == 204) {
System.out.println("success");
} else {
System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity()));
throw new IOException("request failed");
}
} finally {
response.close();
}
}
}
jeecg-boot-master\jeecg-boot\jeecg-boot-module-mp\src\main\java\org\jeecg\modules\mp\util\WxPayUtil.java
package org.jeecg.modules.mp.util;
import org.apache.commons.lang3.time.FastDateFormat;
import java.sql.Timestamp;
import java.util.Date;
import java.util.Random;
import java.util.UUID;
/**
* <p>
* 微信小程序支付工具类
* </p>
*
* @Author DaQiang
* @since 2021-3-15
*/
public class WxPayUtil {
/**
* 生成商户系统内部订单号
*
* @return
*/
public static String createOutTradeNo() {
return FastDateFormat.getInstance("yyyyMMddHHmmssSSS").format(new Date()) + UUID.randomUUID().toString().hashCode();
}
/**
* 生成时间戳
*
* @return
*/
public static String createTimestamp() {
return new Timestamp(System.currentTimeMillis()).toString();
}
/**
* 生成随机数
*
* @return
*/
public static String createNonceStr() {
// return UUID.randomUUID().toString();
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
String res = "";
for (int i = 0; i < 32; i++) {
Random rd = new Random();
res += chars.charAt(rd.nextInt(chars.length() - 1));
}
return res;
}
}
jeecg-boot-master\jeecg-boot\jeecg-boot-base-common\src\main\java\org\jeecg\config\GlobalKeys.java
package org.jeecg.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
/**
* @Description: 系统业务参数配置类
* @author: DaQiang
* @date: 2020年09月29日 11:11
*/
@Component
@ConfigurationProperties(prefix = "sys")
@PropertySource("classpath:systemParam.properties")
@Data
public class GlobalKeys {
private String rootAdcd;
private String rootAdnm;
private String rootChancd;
private String rootChannm;
private String rootDeCd;
private String rootDeNm;
private String rootCd;
private String rootNm;
// 微信小程序
// AppID(小程序ID)
private String appId;
// AppSecret(小程序密钥)
private String secret;
// 微信支付
// 商户号
private String mchId;
// 商户证书序列号
private String mchSerialNo;
// API密钥
private String apiKey;
// APIv3密钥
private String apiV3Key;
}
jeecg-boot-master\jeecg-boot\jeecg-boot-base-common\src\main\resources\systemParam.properties
sys.rootAdcd=1408
sys.rootAdnm=**市
sys.rootChancd=00
sys.rootChannm=****服务中心
sys.rootDeCd=0003
sys.rootDeNm=财务科
sys.rootCd=00
sys.rootNm=****灌区
# 微信小程序
# AppID(小程序ID)
sys.appId=*************替换为自己的**************
# AppSecret(小程序密钥)
sys.secret=*************替换为自己的**************
# 微信支付
# 商户号
sys.mchId=*************替换为自己的**************
# 商户证书序列号
sys.mchSerialNo=*************替换为自己的**************
# API密钥
sys.apiKey=*************替换为自己的**************
# APIv3密钥
sys.apiV3Key=*************替换为自己的**************
jeecg-boot-master\jeecg-boot\apiclient_key.pem
-----BEGIN PRIVATE KEY-----
***************************************
***************************************
***************************************
*************替换为自己的**************
***************************************
***************************************
***************************************
-----END PRIVATE KEY-----