package cn.ktc.jkf.utils;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.http.util.TextUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import com.jfinal.core.JFinal;
import com.jfinal.json.FastJson;
import com.jfinal.json.JFinalJson;
import com.jfinal.kit.HttpKit;
import com.jfinal.kit.Kv;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import okhttp3.HttpUrl;
/**
* 微信
* @author As40098
*
*/
public class WeChatUtils {
// 小程序ID
private static String appid = "wx00000000000000";
// 小程序密钥
private static String appSecret = "1680000000000000000000000000000000";
// 直连商户号
private static String mchId = "160000000000";
// 商户平台设置的密钥
private static String key = "3bonQ85555555555555555555555555555";
// 证书序列号
private static String serialNo = "57F90A5D1F62000000000000000000000";
// 私钥文件地址
private static String privateKeyFileName = "/assets/1622811294_20220331_cert/apiclient_key.pem";
/**
* 获取权限信息
* @param code
* @return String {openid=og5I06q7LiV82tu-m9daktwlKKKg, session_key=ox6rjOTkIIAT+Ujkji14ng==}
*/
public static String getAuthInfo(String code) {
Map data = new HashMap();
data.put("appid", appid);
data.put("secret", appSecret);
data.put("grant_type", "authorization_code");
data.put("js_code", code);
String result = HttpKit.get("https://api.weixin.qq.com/sns/jscode2session", data);
return result;
}
/**
* 获取手机号信息
* @param encryptedData
* @param iv
* @param sessionKey
* @return Kv{"phoneNumber":"13145985931","purePhoneNumber":"13145985931","countryCode":"86","watermark":{"timestamp":1648601482,"appid":"wx5f7c2b02ba91715e"}}
*/
public static String getPhoneNumber(String encryptedData, String iv, String sessionKey) {
try {
byte[] keyByte = Base64.getDecoder().decode(sessionKey);
byte[] dataByte = Base64.getDecoder().decode(encryptedData);
byte[] ivByte = Base64.getDecoder().decode(iv);
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
Key sKeySpec = new SecretKeySpec(keyByte, "AES");
AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
params.init(new IvParameterSpec(ivByte));
cipher.init(Cipher.DECRYPT_MODE, sKeySpec, params);
byte[] result = cipher.doFinal(dataByte);
return new String (result);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 获取支付签名信息
* @return
*/
public static Map getPaySign(String prepayId) {
String signType = "RSA";
Long timeStamp = System.currentTimeMillis() / 1000;
String nonceStr = WeChatUtils.getRandomString(20);
String packageStr = "prepay_id="+prepayId;
String paySign;
try {
String message = appid + "\n" + timeStamp + "\n" + nonceStr + "\n" + packageStr + "\n";
paySign = WeChatUtils.sign(message.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
Map params = new HashMap();
params.put("appid", appid);
params.put("timeStamp", timeStamp.toString());
params.put("nonceStr", nonceStr);
params.put("package", packageStr);
params.put("signType", signType);
params.put("paySign", paySign);
return params;
}
/***
* 统一下单(生成预付单)
* @param ipAddr
* @param openid
* @param orderSn
* @param price
* @return
*/
public static String unifiedOrder(String openid, String orderSn, int price, String notifyUrl) {
try {
Kv amount = Kv.by("total", price).set("currency", "CNY");
Kv payer = Kv.by("openid", openid);
Map params = new HashMap();
params.put("appid", appid);
params.put("mchid", mchId);
params.put("description", "云屏汇-会员充值(开发测试单)");// 商品描述
params.put("out_trade_no", orderSn);// 商户订单号
params.put("notify_url", notifyUrl);// 通知地址
params.put("amount", amount);
params.put("payer", payer);
String postData = JFinalJson.getJson().toJson(params);
String payUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
Map header = WeChatUtils.getAuthorization(payUrl, "POST", postData);
String jsonString = HttpKit.post(payUrl, postData, header);
Kv data = FastJson.getJson().parse(jsonString, Kv.class);
if(data != null) {
String prepayId = data.getStr("prepay_id");
if(prepayId != null) {
return prepayId;
}
}
System.out.println("支付返回结果:"+jsonString);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 商户订单号查询
* @param outTradeNo
*/
public static String getOrder(String outTradeNo) {
String url = String.format("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s?mchid=%s", outTradeNo, mchId);
Map header = WeChatUtils.getAuthorization(url, "GET", "");
try {
String result = HttpKit.get(url, null, header);
System.out.println("查询支付结果:"+result);
return result;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 检查订单是否已经支付
* @param outTradeNo
* @return
*/
public static boolean checkOrderIsPay(String outTradeNo) {
// 获取订单信息
String orderStr = WeChatUtils.getOrder(outTradeNo);
if(!TextUtils.isEmpty(orderStr)) {
Kv data = FastJson.getJson().parse(orderStr, Kv.class);
if(data != null && data.getStr("trade_state").equals("SUCCESS")) {
return true;
}
}
return false;
}
/**
* 异步回调加密参数进行解密
* @param body
* @return
*/
public static String decodeNotify(String body) {
Kv data = FastJson.getJson().parse(body, Kv.class);
Kv resource = FastJson.getJson().parse(data.getStr("resource"), Kv.class);
String associatedData = resource.getStr("associated_data");
String nonceStr = resource.getStr("nonce");
String cipherText = resource.getStr("ciphertext");
AesUtil aesUtil = new AesUtil(key.getBytes(StandardCharsets.UTF_8));
try {
return aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonceStr.getBytes(StandardCharsets.UTF_8), cipherText);
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
return null;
}
/**
* 获取header部分的权限信息
* @param payUrl
* @param method
* @param body
* @return
*/
private static Map getAuthorization(String payUrl, String method, String body) {
String nonceStr = WeChatUtils.getRandomString(16);
long timestamp = System.currentTimeMillis() / 1000;
HttpUrl httpurl = HttpUrl.parse(payUrl);
String canonicalUrl = httpurl.encodedPath();
if (httpurl.encodedQuery() != null) {
canonicalUrl += "?" + httpurl.encodedQuery();
}
String message = method + "\n" + canonicalUrl + "\n" + timestamp + "\n" + nonceStr + "\n" + body + "\n";
String signature = WeChatUtils.sign(message.getBytes());
String authorization = "WECHATPAY2-SHA256-RSA2048 mchid=\"" + mchId + "\"," + "nonce_str=\"" + nonceStr + "\"," + "timestamp=\"" + timestamp + "\"," + "serial_no=\"" + serialNo + "\"," + "signature=\"" + signature + "\"";
Map header = new HashMap();
header.put("Authorization", authorization);
header.put("Content-Type", "application/json");
return header;
}
private static String sign(byte[] message) {
try {
PrivateKey privateKey = WeChatUtils.getPrivateKey();
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(privateKey);
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 获取私钥。
* @param filename 私钥文件路径 (required)
* @return 私钥对象
*/
private static PrivateKey getPrivateKey() {
try {
String privateKeyPath = JFinal.me().getServletContext().getRealPath(privateKeyFileName);
String content = new String(Files.readAllBytes(Paths.get(privateKeyPath)), "utf-8");
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 (Exception e) {
throw new RuntimeException("当前Java环境不支持RSA", e);
}
}
/**
* 获取随机字符串
* @param length
* @return
*/
private static String getRandomString(int length){
//定义一个字符串(A-Z,a-z,0-9)即62位;
String str="zxcvbnmlkjhgfdsaqwertyuiopQWERTYUIOPASDFGHJKLZXCVBNM1234567890";
//由Random生成随机数
Random random=new Random();
StringBuffer sb=new StringBuffer();
//长度为几就循环几次
for(int i=0; i
package cn.ktc.jkf.api;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import org.apache.http.util.TextUtils;
import com.jfinal.core.Controller;
import com.jfinal.json.FastJson;
import com.jfinal.kit.Kv;
import cn.ktc.jkf.utils.WeChatUtils;
/**
* 微信接口
* @author As40098
*
*/
public class WeChatApi extends Controller {
/**
* 存储权限键名
*/
private String authKey = "auth";
/**
* 微信授权,获取openid、session_key
*/
public void setAuth() {
String code = get("code");
String authInfo = WeChatUtils.getAuthInfo(code);
System.out.println("获取微信授权信息:"+authInfo);
setSessionAttr(this.authKey, authInfo);
String sessionId = getSession().getId();
Kv data = Kv.by("JSESSIONID", sessionId);
renderJson(data);
}
/**
* 获取手机号码
*/
public void getPhoneNumber() {
String encryptedData = get("encryptedData");
String iv = get("iv");
String sessionKey = this.getSessionKey();
if(sessionKey == null) {
renderJson(Kv.by("code", 100).set("message", "登录已过期"));
return;
}
String phoneInfo = WeChatUtils.getPhoneNumber(encryptedData, iv, sessionKey);
System.out.println("获取手机号信息:"+phoneInfo);
renderJson(phoneInfo);
}
/**
* 获取支付参数
*/
public void getPaymentInfo() {
int price = 1;
String orderSn = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
String openid = this.getOpenid();
if(openid == null) {
renderJson(Kv.by("code", 100).set("message", "登录已过期"));
return;
}
// 定义返回结果地址
String notifyUrl = getRequest().getRequestURL().toString().split(getRequest().getServletPath())[0]+"/wechat/payNotify";
// 生成预支付订单
String prepayId = WeChatUtils.unifiedOrder(openid, orderSn, price, notifyUrl);
System.out.println("预支付获取prepayId="+prepayId);
if(prepayId == null) {
renderJson(Kv.by("message", "生成预付单失败"));
return;
}
Map paymentInfo = WeChatUtils.getPaySign(prepayId);
if(paymentInfo == null) {
renderJson(Kv.by("message", "支付失败"));
return;
}
paymentInfo.put("orderSn", orderSn);
renderJson(paymentInfo);
}
/**
* 支付异步通知
*/
public void payNotify() {
String params = getRawData();
if(!TextUtils.isEmpty(params)) {
System.out.println("异步通知参数:"+params);
String result = WeChatUtils.decodeNotify(params);
if(!TextUtils.isEmpty(result)) {
Kv data = FastJson.getJson().parse(result, Kv.class);
if(data != null) {
String state = data.getStr("trade_state");
if(state.equals("SUCCESS")) {
String orderSn = data.getStr("out_trade_no");
if(WeChatUtils.checkOrderIsPay(orderSn)) {
// 修改订单信息
// 返回正确结果为微信
renderJson(Kv.by("code", "SUCCESS").set("message","成功"));
return;
}
}
}
}
}
renderJson(Kv.by("code", "FAIL").set("message","失败"));
}
/**
* 检查订单是否已经支付成功
*/
public void checkOrderPayState() {
String orderSn = get("orderSn");
if(!WeChatUtils.checkOrderIsPay(orderSn)) {
renderJson(Kv.by("code", "FAIL").set("message","订单还未支付成功"));
return;
}
renderJson(Kv.by("code", "SUCCESS"));
}
/**
* 获取openid
* @return
*/
private String getOpenid() {
String auth = getSessionAttr(this.authKey);
Kv data = FastJson.getJson().parse(auth, Kv.class);
if(data == null) {
return null;
}
return data.getStr("openid");
}
/**
* 获取session_key
* @return
*/
private String getSessionKey() {
String auth = getSessionAttr(this.authKey);
if(auth == null) {
return null;
}
Kv data = FastJson.getJson().parse(auth, Kv.class);
if(data == null) {
return null;
}
return data.getStr("session_key");
}
}