package org.springblade.modules.pay.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.github.wxpay.sdk.WXPayUtil; import com.google.gson.Gson; import com.wechat.pay.contrib.apache.httpclient.util.AesUtil; import com.wechat.pay.contrib.apache.httpclient.util.PemUtil; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springblade.common.enums.OrderStatus; import org.springblade.common.enums.wxpay.WxApiType; import org.springblade.common.enums.wxpay.WxNotifyType; import org.springblade.modules.pay.controller.WxPayController; import org.springblade.modules.pay.entity.WxPayInfo; import org.springblade.modules.pay.mapper.WxPayMapper; import org.springblade.modules.pay.service.IWxPayService; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.FileInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.*; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; @Service public class WxPayServiceImpl extends ServiceImpl implements IWxPayService { private final String MINI_PROGRAM_APP_ID = "wx41aa8a5d2e565a05"; private static final Logger logger = LoggerFactory.getLogger(WxPayServiceImpl.class); private final ReentrantLock lock = new ReentrantLock(); @Override public Object getOpenId(String code) { return null; } @Override public Map jsapiPay(Long productId) throws IOException { String prepayId = ""; //生成订单 // OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId); logger.info("调用统一下单API"); // //调用统一下单API // HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.JSAPI_PAY.getType())); // // // 请求body参数 // // Gson gson = new Gson(); // Map paramsMap = new HashMap(); // paramsMap.put("appid", wxPayConfig.getAppid()); // paramsMap.put("mchid", wxPayConfig.getMchId()); // paramsMap.put("description", orderInfo.getTitle()); // paramsMap.put("out_trade_no", orderInfo.getOrderNo()); // paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType())); // // Map amountMap = new HashMap(); // amountMap.put("total", orderInfo.getTotalFee()); // amountMap.put("currency", "CNY"); // // paramsMap.put("amount", amountMap); // // Map payerMap = new HashMap(); // payerMap.put("openid", "oSX3G6OUuGaKJbWzSHUpPbzgtDXo"); // // paramsMap.put("payer", payerMap); // // //将参数转换成json字符串 // String jsonParams = gson.toJson(paramsMap); // logger.info("请求参数 ===> {}" + jsonParams); // // StringEntity entity = new StringEntity(jsonParams, "utf-8"); // entity.setContentType("application/json"); // httpPost.setEntity(entity); // httpPost.setHeader("Accept", "application/json"); // // //完成签名并执行请求 // CloseableHttpResponse response = wxPayClient.execute(httpPost); // // try { // String bodyAsString = EntityUtils.toString(response.getEntity());//响应体 // int statusCode = response.getStatusLine().getStatusCode();//响应状态码 // if (statusCode == 200) { //处理成功 // logger.info("成功, 返回结果 = " + bodyAsString); // } else if (statusCode == 204) { //处理成功,无返回Body // logger.info("成功"); // } else { // logger.info("jszpi下单失败,响应码 = " + statusCode + ",返回结果 = " + bodyAsString); // throw new IOException("request failed"); // } // //响应结果 // Map resultMap = gson.fromJson(bodyAsString, HashMap.class); // //预支付交易会话标识 // prepayId = resultMap.get("prepay_id"); // // //返回预支付交易会话标识 // // 组装前端预下单参数 // /** 返回给前端所需要的数据 */ // SortedMap payMap = new TreeMap<>(); // String nonceStr = WXPayUtil.generateNonceStr(); // String timestamp = String.valueOf(System.currentTimeMillis() / 1000); // payMap.put("appId", wxPayConfig.getAppid()); // payMap.put("timeStamp", timestamp); // payMap.put("nonceStr", nonceStr); // payMap.put("signType", "RSA"); // payMap.put("package", "prepay_id=" + prepayId); // String paySign = getSign(wxPayConfig.getAppid(), prepayId, timestamp, nonceStr); // payMap.put("paySign", paySign); // logger.info("返回参数 ===> {}" + gson.toJson(payMap)); // return payMap; // } finally { // response.close(); // } return null; } @Override public void processOrder(Map bodyMap) throws GeneralSecurityException { logger.info("处理订单"); //解密报文 String plainText = decryptFromResource(bodyMap); //将明文转换成map Gson gson = new Gson(); HashMap plainTextMap = gson.fromJson(plainText, HashMap.class); String orderNo = (String) plainTextMap.get("out_trade_no"); /*在对业务数据进行状态检查和处理之前, 要采用数据锁进行并发控制, 以避免函数重入造成的数据混乱*/ //尝试获取锁: // 成功获取则立即返回true,获取失败则立即返回false。不必一直等待锁的释放 // if (lock.tryLock()) { // try { // //处理重复的通知 // //接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的。 // String orderStatus = orderInfoService.getOrderStatus(orderNo); // if (!OrderStatus.NOTPAY.getType().equals(orderStatus)) { // return; // } // // //模拟通知并发 // try { // TimeUnit.SECONDS.sleep(5); // } catch (InterruptedException e) { // e.printStackTrace(); // } // // //更新订单状态 // orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS); // // //记录支付日志 // paymentInfoService.createPaymentInfo(plainText); // } finally { // //要主动释放锁 // lock.unlock(); // } // } } /** * 通过prepay_id获取签名 * * @param appid * @param prepay_id * @param timestamp * @param nonceStr * @return * @throws IOException * @throws SignatureException * @throws NoSuchAlgorithmException * @throws InvalidKeyException */ String getSign(String appid, String prepay_id, String timestamp, String nonceStr) throws IOException, SignatureException, NoSuchAlgorithmException, InvalidKeyException { //从下往上依次生成 String message = getSignStr(appid, timestamp, nonceStr, "prepay_id=" + prepay_id); //签名 String signature = sign(message.getBytes("utf-8")); return signature; } String sign(byte[] message) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException { //签名方式 Signature sign = Signature.getInstance("SHA256withRSA"); //私钥,通过MyPrivateKey来获取,这是个静态类可以接调用方法 ,需要的是_key.pem文件的绝对路径配上文件名 // sign.initSign(getPrivateKey(wxPayConfig.getPrivateKeyPath())); sign.update(message); return Base64.getEncoder().encodeToString(sign.sign()); } /** * 按照前端签名文档规范进行排序 * * @param appId * @param timeStamp * @param nonceStr * @param packageValue * @return */ private String getSignStr(String appId, String timeStamp, String nonceStr, String packageValue) { return String.format("%s\n%s\n%s\n%s\n", appId, timeStamp, nonceStr, packageValue); } private PrivateKey getPrivateKey(String filename) { try { FileInputStream fileInputStream = new FileInputStream(filename); logger.info("文件内容:" + fileInputStream); return PemUtil.loadPrivateKey(fileInputStream); } catch (Exception e) { logger.info("私钥文件不存在", e); throw new RuntimeException("私钥文件不存在", e); } } /** * 对称解密 * * @param bodyMap * @return */ private String decryptFromResource(Map bodyMap) throws GeneralSecurityException { logger.info("密文解密"); //通知数据 Map resourceMap = (Map) bodyMap.get("resource"); //数据密文 String ciphertext = resourceMap.get("ciphertext"); //随机串 String nonce = resourceMap.get("nonce"); //附加数据 String associatedData = resourceMap.get("associated_data"); logger.info("密文 ===> {}", ciphertext); // AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8)); // String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), // nonce.getBytes(StandardCharsets.UTF_8), // ciphertext); // logger.info("明文 ===> {}", plainText); // return plainText; return null; } }