package org.yeshi.utils.wx;
|
|
import com.google.gson.Gson;
|
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 net.sf.json.JSONObject;
|
import org.apache.commons.io.Charsets;
|
import org.apache.commons.io.IOUtils;
|
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.entity.ContentType;
|
import org.apache.http.entity.StringEntity;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
import org.apache.http.util.EntityUtils;
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
import org.yeshi.utils.StringUtil;
|
import org.yeshi.utils.entity.wx.WXAPPInfo;
|
import org.yeshi.utils.entity.wx.WXPayNotifyData;
|
import org.yeshi.utils.entity.wx.WXPayOrderInfoV3;
|
import org.yeshi.utils.entity.wx.WXPlaceOrderParams;
|
import org.yeshi.utils.exception.WXOrderException;
|
import org.yeshi.utils.exception.WXPayException;
|
import org.yeshi.utils.exception.WXPlaceOrderParamsException;
|
|
import javax.crypto.Cipher;
|
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.spec.GCMParameterSpec;
|
import javax.crypto.spec.SecretKeySpec;
|
import javax.servlet.http.HttpServletRequest;
|
import java.io.ByteArrayInputStream;
|
import java.io.FileInputStream;
|
import java.io.IOException;
|
import java.math.BigDecimal;
|
import java.net.URLEncoder;
|
import java.nio.charset.Charset;
|
import java.nio.file.Files;
|
import java.nio.file.Paths;
|
import java.security.*;
|
import java.util.Base64;
|
|
/**
|
* 微信支付帮助类(基于微信支付V3接口)
|
*
|
* @author Administrator
|
*/
|
public class WXPayV3Util {
|
|
static Logger logger = LoggerFactory.getLogger(WXPayV3Util.class);
|
|
private static CloseableHttpClient getHttpClient(WXAPPInfo app) throws Exception {
|
// 加载商户私钥(privateKey:私钥字符串)
|
PrivateKey merchantPrivateKey = PemUtil
|
.loadPrivateKey(new ByteArrayInputStream(app.getPrivateKey().getBytes("utf-8")));
|
|
// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3秘钥)
|
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
|
new WechatPay2Credentials(app.getMchId(), new PrivateKeySigner(app.getMchSerialNo(), merchantPrivateKey)), app.getApiV3Key().getBytes("utf-8"));
|
|
// 初始化httpClient
|
return WechatPayHttpClientBuilder.create()
|
.withMerchant(app.getMchId(), app.getMchSerialNo(), merchantPrivateKey)
|
.withValidator(new WechatPay2Validator(verifier)).build();
|
}
|
|
/**
|
* 网络请求
|
*
|
* @param url
|
* @param requestData
|
* @param app
|
* @return
|
* @throws Exception
|
*/
|
private static JSONObject request(String url, String requestData, WXAPPInfo app) throws Exception {
|
HttpPost httpPost = new HttpPost(url);
|
if (StringUtil.isNullOrEmpty(requestData)) {
|
requestData = "{}";
|
}
|
|
if (!StringUtil.isNullOrEmpty(requestData)) {
|
StringEntity entity = new StringEntity(requestData, ContentType.APPLICATION_JSON.withCharset(Charset.forName("UTF-8")));
|
entity.setContentType("application/json;charset=utf-8");
|
httpPost.setEntity(entity);
|
}
|
httpPost.setHeader("Accept", "application/json;charset=utf-8");
|
|
//完成签名并执行请求
|
CloseableHttpClient httpClient = getHttpClient(app);
|
CloseableHttpResponse response = httpClient.execute(httpPost);
|
try {
|
int statusCode = response.getStatusLine().getStatusCode();
|
if (statusCode == 200) {
|
System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
|
String result = EntityUtils.toString(response.getEntity());
|
JSONObject resultJson = JSONObject.fromObject(result);
|
return resultJson;
|
} else if (statusCode == 204) {
|
System.out.println("success");
|
} else {
|
logger.error("微信支付请求出错,code:{} body{}",statusCode, EntityUtils.toString(response.getEntity()));
|
throw new Exception("request failed");
|
}
|
} finally {
|
response.close();
|
}
|
return null;
|
}
|
|
|
private static JSONObject requestGet(String url, String requestData, WXAPPInfo app) throws Exception {
|
HttpGet httpGet = new HttpGet(url);
|
|
if (StringUtil.isNullOrEmpty(requestData)) {
|
requestData = "{}";
|
}
|
|
// if (!StringUtil.isNullOrEmpty(requestData)) {
|
// StringEntity entity = new StringEntity(requestData, ContentType.APPLICATION_JSON.withCharset(Charset.forName("UTF-8")));
|
// entity.setContentType("application/json;charset=utf-8");
|
// httpPost.setEntity(entity);
|
// }
|
httpGet.setHeader("Accept", "application/json;charset=utf-8");
|
|
//完成签名并执行请求
|
CloseableHttpClient httpClient = getHttpClient(app);
|
CloseableHttpResponse response = httpClient.execute(httpGet);
|
try {
|
int statusCode = response.getStatusLine().getStatusCode();
|
if (statusCode == 200) {
|
System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
|
String result = EntityUtils.toString(response.getEntity());
|
JSONObject resultJson = JSONObject.fromObject(result);
|
return resultJson;
|
} else if (statusCode == 204) {
|
System.out.println("success");
|
} else {
|
System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity()));
|
throw new Exception("request failed");
|
}
|
} finally {
|
response.close();
|
}
|
return null;
|
}
|
|
/**
|
* H5支付统一下单接口
|
*
|
* @param params
|
* @return 支付链接
|
* @throws WXPlaceOrderParamsException
|
* @throws Exception
|
*/
|
public static String createH5Order(WXPlaceOrderParams params, String redirectUrl) throws WXPlaceOrderParamsException, Exception {
|
if (params == null)
|
throw new WXPlaceOrderParamsException(1, "请传入下单参数");
|
|
if (params.getApp() == null)
|
throw new WXPlaceOrderParamsException(2, "请传入下单应用信息");
|
|
if (StringUtil.isNullOrEmpty(params.getApp().getAppId()))
|
throw new WXPlaceOrderParamsException(201, "请传入下单应用信息-appId");
|
|
if (StringUtil.isNullOrEmpty(params.getApp().getMchId()))
|
throw new WXPlaceOrderParamsException(203, "请传入下单应用信息-mchId");
|
if (StringUtil.isNullOrEmpty(params.getApp().getApiV3Key()))
|
throw new WXPlaceOrderParamsException(204, "请传入下单应用信息apiV3Key");
|
if (StringUtil.isNullOrEmpty(params.getBody()))
|
throw new WXPlaceOrderParamsException(3, "请传入body");
|
|
if (StringUtil.isNullOrEmpty(params.getOrderNo()))
|
throw new WXPlaceOrderParamsException(4, "请传入orderNo");
|
|
if (params.getFee() == null)
|
throw new WXPlaceOrderParamsException(5, "请传入fee");
|
|
if (StringUtil.isNullOrEmpty(params.getIp()))
|
throw new WXPlaceOrderParamsException(6, "请传入ip");
|
|
if (StringUtil.isNullOrEmpty(params.getNotifyUrl()))
|
throw new WXPlaceOrderParamsException(7, "请传入notifyUrl");
|
|
// 请求body参数
|
String reqdata = "{"
|
+ "\"amount\": {"
|
+ "\"total\": " + params.getFee().multiply(new BigDecimal(100)).intValue() + ","
|
+ "\"currency\": \"CNY\""
|
+ "},"
|
+ "\"scene_info\": {"
|
+ "\"payer_client_ip\":\"" + params.getIp() + "\","
|
+ "\"h5_info\": {"
|
+ "\"type\": \"Wap\"" + "}},"
|
+ "\"mchid\": \"" + params.getApp().getMchId() + "\","
|
+ "\"description\": \"" + params.getBody() + "\","
|
+ "\"notify_url\": \"" + params.getNotifyUrl() + "\",";
|
//附加数据,在支付结果通知中会原样返回
|
if (params.getAttach() != null)
|
reqdata += ("\"attach\": \"" + params.getAttach() + "\",");
|
|
reqdata += ("\"out_trade_no\": \"" + params.getOrderNo() + "\","
|
+ "\"appid\": \"" + params.getApp().getAppId() + "\"" + "}");
|
JSONObject result = request("https://api.mch.weixin.qq.com/v3/pay/transactions/h5", reqdata, params.getApp());
|
if (result == null)
|
return null;
|
return result.optString("h5_url") + "&redirect_url=" + URLEncoder.encode(redirectUrl, "UTF-8");
|
}
|
|
/**
|
* 查询订单号是否支付成功
|
*
|
* @param orderNo
|
* @param app
|
* @return
|
* @throws WXOrderException
|
*/
|
public static boolean isPaySuccess(String orderNo, WXAPPInfo app) throws Exception {
|
WXPayOrderInfoV3 info = getPayOrderInfo(orderNo, app);
|
if (info == null)
|
return false;
|
return "SUCCESS".equalsIgnoreCase(info.getTrade_state());
|
}
|
|
|
/**
|
* 获取订单信息
|
*
|
* @param orderNo
|
* @param app
|
* @return
|
* @throws Exception
|
*/
|
public static WXPayOrderInfoV3 getPayOrderInfo(String orderNo, WXAPPInfo app) throws Exception {
|
String url = String.format("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s?mchid=%s", URLEncoder.encode(orderNo), app.getMchId());
|
JSONObject result = requestGet(url, "", app);
|
if (result == null)
|
return null;
|
return new Gson().fromJson(result.toString(), WXPayOrderInfoV3.class);
|
}
|
|
/**
|
* 获取支付回调数据
|
*
|
* @param request
|
* @return
|
*/
|
public static WXPayNotifyData getPayNotifyContent(HttpServletRequest request) throws WXPayException {
|
WXPayNotifyData notifyData = new WXPayNotifyData();
|
//验证证书序列号
|
String mchSerialNo = request.getHeader("Wechatpay-Serial");
|
String timeStamp = request.getHeader("Wechatpay-Timestamp");
|
String nonce = request.getHeader("Wechatpay-Nonce");
|
String signature = request.getHeader("Wechatpay-Signature");
|
String data = null;
|
|
try {
|
if (request.getInputStream() != null) {
|
String entity = IOUtils.toString(request.getInputStream(), "UTF-8");
|
data = entity;
|
}
|
} catch (IOException e) {
|
e.printStackTrace();
|
}
|
|
if (StringUtil.isNullOrEmpty(data)) {
|
throw new WXPayException(2, "通知的内容为空");
|
}
|
|
notifyData.setEntity(data);
|
notifyData.setMchSerialNo(mchSerialNo);
|
notifyData.setNonce(nonce);
|
notifyData.setSignature(signature);
|
notifyData.setTimeStamp(timeStamp);
|
|
return notifyData;
|
}
|
|
|
/**
|
* 获取支付成功的外部订单号
|
*
|
* @param decodeData -反编码后的数据
|
* @param app
|
* @return
|
* @throws WXPayException
|
*/
|
public static String getPaySuccessOutOrderNo(String decodeData, WXAPPInfo app) throws WXPayException {
|
JSONObject resultJson = JSONObject.fromObject(decodeData);
|
String eventType = resultJson.optString("event_type");
|
switch (eventType) {
|
//支付成功
|
case "TRANSACTION.SUCCESS":
|
JSONObject resource = resultJson.optJSONObject("resource");
|
String ciphertext = resource.optString("ciphertext");
|
String r = null;
|
try {
|
r = decryptToString(app.getApiV3Key(), resource.optString("associated_data"), resource.optString("nonce"), ciphertext);
|
} catch (GeneralSecurityException e) {
|
e.printStackTrace();
|
} catch (IOException e) {
|
e.printStackTrace();
|
}
|
|
if (r == null) {
|
throw new WXPayException(1001, "内容解密失败");
|
}
|
//解密格式如下 {"mchid":"1520950211","appid":"wxa99686bb65a9f466","out_trade_no":"buwan-vip-8","transaction_id":"4200000796202101259681241680","trade_type":"MWEB","trade_state":"SUCCESS","trade_state_desc":"支付成功","bank_type":"OTHERS","attach":"","success_time":"2021-01-25T16:18:33+08:00","payer":{"openid":"oq7R20lxhKF8qSnkszxFJHViyKEY"},"amount":{"total":10,"payer_total":10,"currency":"CNY","payer_currency":"CNY"}}
|
JSONObject decript = JSONObject.fromObject(r);
|
String outTradeNo = decript.optString("out_trade_no");
|
String appId = decript.optString("appid");
|
String mchId = decript.optString("mchid");
|
String tradeState = decript.optString("trade_state");
|
//支付成功
|
if (tradeState.equalsIgnoreCase("SUCCESS")) {
|
return outTradeNo;
|
} else {
|
throw new WXPayException(10001, "支付未成功:" + tradeState);
|
}
|
default:
|
throw new WXPayException(101, "支付未成功:" + eventType);
|
}
|
}
|
|
private static String decryptToString(String apiV3Key, String associatedData, String nonce, String ciphertext)
|
throws GeneralSecurityException, IOException {
|
try {
|
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
|
SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes("UTF-8"), "AES");
|
GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes("UTF-8"));
|
|
cipher.init(Cipher.DECRYPT_MODE, key, spec);
|
cipher.updateAAD(associatedData.getBytes("UTF-8"));
|
return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8");
|
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
throw new IllegalStateException(e);
|
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
|
throw new IllegalArgumentException(e);
|
}
|
}
|
|
|
public static void main(String[] args) {
|
|
|
String privateKey = "";
|
try {
|
String content = IOUtils.toString(new FileInputStream("D:\\项目\\返利券\\商户平台\\1520950211_20210125_cert\\apiclient_key.pem"));
|
privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
|
.replace("-----END PRIVATE KEY-----", "")
|
.replaceAll("\\s+", "");
|
} catch (Exception e) {
|
|
}
|
WXAPPInfo app = new WXAPPInfo("wxa99686bb65a9f466", "1520950211", "454328C324C6CC21355D064B44D6524CD7506DD0", privateKey, "XYJkJ2018FAfaodCCx899mLl138rfGVd");
|
|
|
WXPlaceOrderParams params = new WXPlaceOrderParams();
|
params.setBody("影视大全VIP-包月");
|
params.setFee(new BigDecimal("0.1"));
|
params.setNotifyUrl("http://api.ysdq.yeshitv.com:8089/BuWan/wx/pay/vip");
|
params.setOrderNo("buwan-vip-8");
|
params.setIp("113.249.192.231");
|
params.setApp(app);
|
try {
|
String payUrl = WXPayV3Util.createH5Order(params, "http://vip.ysdq.yeshitv.com/wx_result.html");
|
System.out.println(payUrl);
|
} catch (Exception e) {
|
e.printStackTrace();
|
}
|
|
try {
|
WXPayOrderInfoV3 info = WXPayV3Util.getPayOrderInfo("buwan-vip-8", app);
|
System.out.println(info);
|
} catch (Exception e) {
|
e.printStackTrace();
|
}
|
}
|
|
|
}
|