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<WxPayMapper,WxPayInfo> 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<String, String> 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<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
|
// //预支付交易会话标识
|
// prepayId = resultMap.get("prepay_id");
|
//
|
// //返回预支付交易会话标识
|
// // 组装前端预下单参数
|
// /** 返回给前端所需要的数据 */
|
// SortedMap<String, String> 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<String, Object> 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<String, Object> bodyMap) throws GeneralSecurityException {
|
|
logger.info("密文解密");
|
|
//通知数据
|
Map<String, String> 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;
|
}
|
|
}
|