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(); } } }