package com.farriver.bwf.web.controller.wechat; import com.alibaba.fastjson2.JSON; import com.farriver.bwf.common.model.ApiData; import com.farriver.bwf.common.utilities.HttpUtil; import com.farriver.bwf.data.transferobject.wechat.*; import okhttp3.HttpUrl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import jakarta.servlet.http.HttpServletRequest; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.Signature; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Base64; import java.util.UUID; @Component @Service @CacheConfig(cacheNames = "bwf_WXPayViaGZHConfig_cache") public class WXPayViaGZHConfig { private static final Logger logger = LoggerFactory.getLogger(WXPayViaGZHConfig.class); @Value("${wx.gzh_appid}") private String gzh_appid = ""; @Value("${wx.gzh_secret}") private String gzh_secret = ""; @Value("${wx.gzh_notify_url}") private String gzh_notify_url = ""; @Value("${wx.mch_id}") private String mch_id = ""; @Value("${wx.paykey}") private String paykey = ""; @Value("${wx.serialNo}") private String serialNo = ""; @Value("${wx.privateKey}") private String privateKey = ""; public ApiData GetWeChatConfig() { WeChatConfigForGZHModel config = new WeChatConfigForGZHModel(); config.setAppId(gzh_appid); config.setMch_id(mch_id); // config.setPaykey(paykey); // config.setNotify_url(gzh_notify_url); // config.setSecret(gzh_secret); // config.setSerialNo(serialNo); // config.setPrivateKey(privateKey); return ApiData.ok("", config); } public ApiData PayUseWX(WeChatPayConfigViewModel model) { try { //Check data if (model == null || model.getCode().isEmpty() || model.getOrderid().isEmpty() || model.getTotalprice().isEmpty()) { return ApiData.error("微信支付异常,请重新打开公众号"); } //获取用户终端IP RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); String userip = request.getRemoteAddr(); //获取支付配置 return PayUseWXV3(model.getRequesturl(), model.getCode(), userip, model.getOrderid(), model.getTotalprice(), model.getProductname()); } catch (Exception ex) { ex.printStackTrace(); logger.debug("PayUseWX.Exception: " + ex.getMessage()); return ApiData.error("微信支付配置异常,请选择其他支付方式"); } } @Cacheable public ApiData PayUseWXV3(String requesturl, String code, String userip, String orderid, String totalprice, String productname) throws Exception { WeChatConfigModel config = new WeChatConfigModel(); if (gzh_appid.isEmpty()) { return ApiData.error("微信支付参数appid异常,请重新打开公众号"); } if (mch_id.isEmpty()) { return ApiData.error("微信支付参数mch_id异常,请重新打开公众号"); } if (gzh_secret.isEmpty()) { return ApiData.error("微信支付参数secret异常,请重新打开公众号"); } if (code.isEmpty()) { return ApiData.error("微信支付参数code异常,请重新打开公众号"); } if (userip.isEmpty()) { return ApiData.error("微信支付参数userip异常,请重新打开公众号"); } if (paykey.isEmpty()) { return ApiData.error("微信支付参数paykey异常,请重新打开公众号"); } if (gzh_notify_url.isEmpty()) { return ApiData.error("微信支付参数notify_url异常,请重新打开公众号"); } if (orderid.isEmpty()) { return ApiData.error("微信支付参数orderid异常,请重新打开公众号"); } if (totalprice.isEmpty()) { return ApiData.error("微信支付参数totalprice异常,请重新打开公众号"); } double iTotalPrice = -1; int total_fee = -1; try { iTotalPrice = Double.parseDouble(totalprice) * 100; String iTotalPriceStr = iTotalPrice + ""; String[] iTotalPriceStrArr = iTotalPriceStr.split("\\."); String iTotalPriceStr2 = iTotalPriceStrArr[0]; total_fee = Integer.parseInt(iTotalPriceStr2); if (total_fee <= 0) { return ApiData.error("微信支付total_fee异常,请重新打开公众号"); } logger.debug("total_fee: " + total_fee); } catch (Exception exception) { return ApiData.error("微信支付total_fee异常,请重新打开公众号"); } String nonceStr = CreateNonce(); long timestamp = CreateTimestamp(); // 获取统一下单号 GongZhongHaoAccessToken AccessToken = GetAccessToken(gzh_appid, gzh_secret, code); logger.debug("getAccessTokenUri: " + JSON.toJSONString(AccessToken)); String openid = AccessToken.getOpenid(); logger.debug("openid: " + openid); if (openid.isEmpty()) { return ApiData.error("微信支付openid异常,请重新打开公众号"); } StringBuilder str = new StringBuilder(); str.append("{"); str.append(String.format("\"mchid\":\"%s\",", mch_id)); str.append(String.format("\"out_trade_no\":\"%s\",", orderid.substring(0, 30))); str.append("\"appid\":\"" + gzh_appid + "\","); str.append("\"description\":\"" + productname + "\","); str.append("\"notify_url\":\"" + gzh_notify_url + "\","); //支付成功后的通知地址 str.append("\"amount\":{"); str.append(String.format("\"total\":%s,", total_fee)); //支付金额 str.append("\"currency\":\"CNY\""); str.append("},"); str.append("\"payer\":{"); str.append("\"openid\":\"" + openid + "\""); str.append("}"); str.append("}"); String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"; String body = str.toString(); String authorization = GetAuthorization(url, "POST", body, timestamp, nonceStr); String transactionsResponse = HttpUtil.Post(url, body, authorization); logger.debug("PayUseWXV3.transactionsResponse:" + transactionsResponse); TransactionsOrderPay gongZhongHaoAccessToken = JSON.parseObject(transactionsResponse, TransactionsOrderPay.class); String preid = gongZhongHaoAccessToken.getPrepay_id(); logger.debug("PayUseWXV3.preid:" + preid); if (preid.isEmpty()) { return ApiData.error("微信支付参数preid异常,请选择其他支付方式"); } String paySignmessage = String.format("%s\n%s\n%s\nprepay_id=%s\n", gzh_appid, timestamp, nonceStr, preid); logger.debug("GetAuthorization.paySignmessage:" + paySignmessage); String paySign = Sign(paySignmessage, privateKey); logger.debug("GetAuthorization.paySign:" + paySign); config.setAppId(gzh_appid); config.setTimestamp(timestamp + ""); config.setNonceStr(nonceStr); config.setPaySign(paySign); config.setSignType("RSA"); config.setPrepay_id(preid); logger.debug("WeChatConfigModel: " + JSON.toJSONString(config)); return ApiData.ok("操作成功!", config); } @Cacheable public GongZhongHaoAccessToken GetAccessToken(String appid, String secret, String code) throws Exception { String getAccessTokenUri = String.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code", appid, secret, code); String tokenData = HttpUtil.Get(getAccessTokenUri); GongZhongHaoAccessToken data = JSON.parseObject(tokenData, GongZhongHaoAccessToken.class); if (data != null && data.getOpenid() != null && !data.getOpenid().isEmpty()) { return data; } else { return null; } } public String CreateNonce() { return UUID.randomUUID().toString(); } public long CreateTimestamp() { return System.currentTimeMillis() / 1000; } protected String GetAuthorization(String url, String method, String body, long timestamp, String nonceStr) { String message = buildMessage(method, HttpUrl.get(url), timestamp, nonceStr, body); String signature = Sign(message, privateKey); return "WECHATPAY2-SHA256-RSA2048 mchid=\"" + mch_id + "\"," + "nonce_str=\"" + nonceStr + "\"," + "timestamp=\"" + timestamp + "\"," + "serial_no=\"" + serialNo + "\"," + "signature=\"" + signature + "\""; } protected String Sign(String message, String privateKey) { logger.debug("Sign.message:" + message); logger.debug("Sign.privateKey:" + privateKey); try { Signature sign = Signature.getInstance("SHA256withRSA"); sign.initSign(getPrivateKey(privateKey)); sign.update(message.getBytes("utf-8")); return Base64.getEncoder().encodeToString(sign.sign()); } catch (Exception ex) { ex.printStackTrace(); logger.debug(ex.getMessage()); } return null; } public PrivateKey getPrivateKey(String privateKey) throws Exception { byte[] keyBytes = privateKey.getBytes(); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory kf = KeyFactory.getInstance("RSA"); return kf.generatePrivate(spec); } protected String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) { String canonicalUrl = url.encodedPath(); if (url.encodedQuery() != null) { canonicalUrl += "?" + url.encodedQuery(); } return method + "\n" + canonicalUrl + "\n" + timestamp + "\n" + nonceStr + "\n" + body + "\n"; } }