| | |
| | | package org.springblade.modules.pay.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | 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.HttpGet; |
| | | import org.apache.http.client.methods.HttpPost; |
| | | 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.springblade.common.config.WxPayConfig; |
| | | import org.springblade.common.enums.OrderStatus; |
| | | import org.springblade.common.enums.wxpay.WxApiType; |
| | | import org.springblade.common.enums.wxpay.WxNotifyType; |
| | | import org.springblade.common.enums.wxpay.WxRefundStatus; |
| | | import org.springblade.common.enums.wxpay.WxTradeState; |
| | | import org.springblade.common.exception.CustomException; |
| | | import org.springblade.common.utils.HttpUtils; |
| | | import org.springblade.common.utils.SpringUtils; |
| | | import org.springblade.common.utils.WechatPay2ValidatorForRequest; |
| | | import org.springblade.core.secure.utils.AuthUtil; |
| | | import org.springblade.modules.pay.entity.OrderInfoEntity; |
| | | import org.springblade.modules.pay.entity.RefundInfoEntity; |
| | | import org.springblade.modules.pay.entity.WxPayInfo; |
| | | import org.springblade.modules.pay.mapper.WxPayMapper; |
| | | import org.springblade.modules.pay.service.IOrderInfoService; |
| | | import org.springblade.modules.pay.service.IPaymentInfoService; |
| | | import org.springblade.modules.pay.service.IRefundInfoService; |
| | | import org.springblade.modules.pay.service.IWxPayService; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springblade.modules.system.entity.User; |
| | | import org.springblade.modules.system.service.IUserService; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.io.FileInputStream; |
| | | import java.io.IOException; |
| | | import java.math.BigDecimal; |
| | | 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 { |
| | | public class WxPayServiceImpl extends ServiceImpl<WxPayMapper, WxPayInfo> implements IWxPayService { |
| | | private static Logger logger = LoggerFactory.getLogger(WxPayServiceImpl.class); |
| | | |
| | | private final String MINI_PROGRAM_APP_ID = "wx41aa8a5d2e565a05"; |
| | | |
| | | @Resource |
| | | private WxPayConfig wxPayConfig; |
| | | |
| | | // @Resource |
| | | // private CloseableHttpClient wxPayClient; |
| | | |
| | | @Resource |
| | | private IOrderInfoService orderInfoService; |
| | | |
| | | @Resource |
| | | private IPaymentInfoService paymentInfoService; |
| | | |
| | | @Resource |
| | | private IRefundInfoService refundsInfoService; |
| | | |
| | | private final ReentrantLock lock = new ReentrantLock(); |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | @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(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 用户取消订单 |
| | | * |
| | | * @param orderNo |
| | | */ |
| | | @Override |
| | | public void cancelOrder(String orderNo) throws Exception { |
| | | |
| | | //调用微信支付的关单接口 |
| | | this.closeOrder(orderNo); |
| | | |
| | | //更新商户端的订单状态 |
| | | orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CANCEL); |
| | | } |
| | | |
| | | @Override |
| | | public Object getOpenId(String code) { |
| | | public String queryOrder(String orderNo) throws Exception { |
| | | |
| | | logger.info("查单接口调用 ===> {}", orderNo); |
| | | |
| | | String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderNo); |
| | | url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId()); |
| | | |
| | | HttpGet httpGet = new HttpGet(url); |
| | | httpGet.setHeader("Accept", "application/json"); |
| | | |
| | | //完成签名并执行请求 |
| | | CloseableHttpResponse response = null;//wxPayClient.execute(httpGet); |
| | | |
| | | 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("查单接口调用,响应码 = " + statusCode + ",返回结果 = " + bodyAsString); |
| | | throw new IOException("request failed"); |
| | | } |
| | | |
| | | return null; |
| | | return bodyAsString; |
| | | |
| | | } finally { |
| | | response.close(); |
| | | } |
| | | |
| | | } |
| | | |
| | | /** |
| | | * 根据订单号查询微信支付查单接口,核实订单状态 |
| | | * 如果订单已支付,则更新商户端订单状态,并记录支付日志 |
| | | * 如果订单未支付,则调用关单接口关闭订单,并更新商户端订单状态 |
| | | * |
| | | * @param orderNo |
| | | */ |
| | | @Transactional(rollbackFor = Exception.class) |
| | | @Override |
| | | public void checkOrderStatus(String orderNo) throws Exception { |
| | | |
| | | logger.info("根据订单号核实订单状态 ===> {}", orderNo); |
| | | |
| | | //调用微信支付查单接口 |
| | | String result = this.queryOrder(orderNo); |
| | | |
| | | Gson gson = new Gson(); |
| | | Map<String, String> resultMap = gson.fromJson(result, HashMap.class); |
| | | |
| | | //获取微信支付端的订单状态 |
| | | String tradeState = resultMap.get("trade_state"); |
| | | |
| | | //判断订单状态 |
| | | if (WxTradeState.SUCCESS.getType().equals(tradeState)) { |
| | | |
| | | logger.info("核实订单已支付 ===> {}", orderNo); |
| | | |
| | | //如果确认订单已支付则更新本地订单状态 |
| | | orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS); |
| | | //记录支付日志 |
| | | paymentInfoService.createPaymentInfo(result); |
| | | } |
| | | |
| | | if (WxTradeState.NOTPAY.getType().equals(tradeState)) { |
| | | logger.info("核实订单未支付 ===> {}", orderNo); |
| | | |
| | | //如果订单未支付,则调用关单接口 |
| | | this.closeOrder(orderNo); |
| | | |
| | | //更新本地订单状态 |
| | | orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CLOSED); |
| | | } |
| | | |
| | | } |
| | | |
| | | /** |
| | | * 退款 |
| | | * |
| | | * @param orderNo |
| | | * @param reason |
| | | * @throws IOException |
| | | */ |
| | | @Transactional(rollbackFor = Exception.class) |
| | | @Override |
| | | public void refund(String orderNo, String reason) throws Exception { |
| | | |
| | | logger.info("创建退款单记录"); |
| | | //根据订单编号创建退款单 |
| | | RefundInfoEntity refundsInfo = refundsInfoService.createRefundByOrderNo(orderNo, reason); |
| | | |
| | | logger.info("调用退款API"); |
| | | |
| | | //调用统一下单API |
| | | String url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType()); |
| | | HttpPost httpPost = new HttpPost(url); |
| | | |
| | | // 请求body参数 |
| | | Gson gson = new Gson(); |
| | | Map paramsMap = new HashMap(); |
| | | paramsMap.put("out_trade_no", orderNo);//订单编号 |
| | | paramsMap.put("out_refund_no", refundsInfo.getRefundNo());//退款单编号 |
| | | paramsMap.put("reason", reason);//退款原因 |
| | | paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款通知地址 |
| | | |
| | | Map amountMap = new HashMap(); |
| | | amountMap.put("refund", refundsInfo.getRefund());//退款金额 |
| | | amountMap.put("total", refundsInfo.getTotalFee());//原订单金额 |
| | | amountMap.put("currency", "CNY");//退款币种 |
| | | paramsMap.put("amount", amountMap); |
| | | |
| | | //将参数转换成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 =null;// 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) { |
| | | logger.info("成功"); |
| | | } else { |
| | | throw new RuntimeException("退款异常, 响应码 = " + statusCode + ", 退款返回结果 = " + bodyAsString); |
| | | } |
| | | |
| | | //更新订单状态 |
| | | orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_PROCESSING); |
| | | |
| | | //更新退款单 |
| | | refundsInfoService.updateRefund(bodyAsString); |
| | | |
| | | } finally { |
| | | response.close(); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 查询退款接口调用 |
| | | * |
| | | * @param refundNo |
| | | * @return |
| | | */ |
| | | @Override |
| | | public String queryRefund(String refundNo) throws Exception { |
| | | |
| | | logger.info("查询退款接口调用 ===> {}", refundNo); |
| | | |
| | | String url = String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType(), refundNo); |
| | | url = wxPayConfig.getDomain().concat(url); |
| | | |
| | | //创建远程Get 请求对象 |
| | | HttpGet httpGet = new HttpGet(url); |
| | | httpGet.setHeader("Accept", "application/json"); |
| | | |
| | | //完成签名并执行请求 |
| | | CloseableHttpResponse response = null;//wxPayClient.execute(httpGet); |
| | | |
| | | try { |
| | | String bodyAsString = EntityUtils.toString(response.getEntity()); |
| | | int statusCode = response.getStatusLine().getStatusCode(); |
| | | if (statusCode == 200) { |
| | | logger.info("成功, 查询退款返回结果 = " + bodyAsString); |
| | | } else if (statusCode == 204) { |
| | | logger.info("成功"); |
| | | } else { |
| | | throw new RuntimeException("查询退款异常, 响应码 = " + statusCode + ", 查询退款返回结果 = " + bodyAsString); |
| | | } |
| | | |
| | | return bodyAsString; |
| | | |
| | | } finally { |
| | | response.close(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据退款单号核实退款单状态 |
| | | * |
| | | * @param refundNo |
| | | * @return |
| | | */ |
| | | @Transactional(rollbackFor = Exception.class) |
| | | @Override |
| | | public void checkRefundStatus(String refundNo) throws Exception { |
| | | |
| | | logger.info("根据退款单号核实退款单状态 ===> {}", refundNo); |
| | | |
| | | //调用查询退款单接口 |
| | | String result = this.queryRefund(refundNo); |
| | | |
| | | //组装json请求体字符串 |
| | | Gson gson = new Gson(); |
| | | Map<String, String> resultMap = gson.fromJson(result, HashMap.class); |
| | | |
| | | //获取微信支付端退款状态 |
| | | String status = resultMap.get("status"); |
| | | |
| | | String orderNo = resultMap.get("out_trade_no"); |
| | | |
| | | if (WxRefundStatus.SUCCESS.getType().equals(status)) { |
| | | |
| | | logger.info("核实订单已退款成功 ===> {}", refundNo); |
| | | |
| | | //如果确认退款成功,则更新订单状态 |
| | | orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS); |
| | | |
| | | //更新退款单 |
| | | refundsInfoService.updateRefund(result); |
| | | } |
| | | |
| | | if (WxRefundStatus.ABNORMAL.getType().equals(status)) { |
| | | |
| | | logger.info("核实订单退款异常 ===> {}", refundNo); |
| | | |
| | | //如果确认退款成功,则更新订单状态 |
| | | orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_ABNORMAL); |
| | | |
| | | //更新退款单 |
| | | refundsInfoService.updateRefund(result); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 处理退款单 |
| | | */ |
| | | @Transactional(rollbackFor = Exception.class) |
| | | @Override |
| | | public void processRefund(Map<String, Object> bodyMap) throws Exception { |
| | | |
| | | 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"); |
| | | |
| | | if (lock.tryLock()) { |
| | | try { |
| | | |
| | | String orderStatus = orderInfoService.getOrderStatus(orderNo); |
| | | if (!OrderStatus.REFUND_PROCESSING.getType().equals(orderStatus)) { |
| | | return; |
| | | } |
| | | |
| | | //更新订单状态 |
| | | orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS); |
| | | |
| | | //更新退款单 |
| | | refundsInfoService.updateRefund(plainText); |
| | | |
| | | } finally { |
| | | //要主动释放锁 |
| | | lock.unlock(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public Map<String, String> jsapiPay(Long properChargeRecordId) throws IOException { |
| | | |
| | | String prepayId = ""; |
| | | //生成订单 |
| | | OrderInfoEntity orderInfo = orderInfoService.getOne(Wrappers.<OrderInfoEntity>lambdaQuery().eq(OrderInfoEntity::getProperChargeRecordId, properChargeRecordId)); |
| | | if (orderInfo.getTotalFee() <= 0) { |
| | | throw new CustomException("支付金额不能低于等于0元"); |
| | | } |
| | | IUserService userService = SpringUtils.getBean(IUserService.class); |
| | | User serviceOne = userService.getOne(Wrappers.<User>lambdaQuery().eq(User::getId, AuthUtil.getUserId())); |
| | | |
| | | 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", serviceOne.getMiniOpenId()); |
| | | |
| | | paramsMap.put("payer", payerMap); |
| | | |
| | | // 是否指定分账,枚举值 true:是 false:否 |
| | | paramsMap.put("profit_sharing", true); |
| | | |
| | | //将参数转换成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 = null;//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; |
| | | } catch (IOException e) { |
| | | throw new RuntimeException(e); |
| | | } catch (SignatureException e) { |
| | | throw new RuntimeException(e); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new RuntimeException(e); |
| | | } catch (InvalidKeyException e) { |
| | | throw new RuntimeException(e); |
| | | } finally { |
| | | response.close(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 通过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 orderNo |
| | | */ |
| | | private void closeOrder(String orderNo) throws Exception { |
| | | |
| | | logger.info("关单接口的调用,订单号 ===> {}", orderNo); |
| | | //创建远程请求对象 |
| | | String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderNo); |
| | | url = wxPayConfig.getDomain().concat(url); |
| | | HttpPost httpPost = new HttpPost(url); |
| | | |
| | | //组装json请求体 |
| | | Gson gson = new Gson(); |
| | | Map<String, String> paramsMap = new HashMap<>(); |
| | | paramsMap.put("mchid", wxPayConfig.getMchId()); |
| | | 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 = null;//wxPayClient.execute(httpPost); |
| | | |
| | | try { |
| | | int statusCode = response.getStatusLine().getStatusCode();//响应状态码 |
| | | if (statusCode == 200) { //处理成功 |
| | | logger.info("成功200"); |
| | | } else if (statusCode == 204) { //处理成功,无返回Body |
| | | logger.info("成功204"); |
| | | } else { |
| | | logger.info("Native下单失败,响应码 = " + statusCode); |
| | | throw new IOException("request failed"); |
| | | } |
| | | |
| | | } finally { |
| | | response.close(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 对称解密 |
| | | * |
| | | * @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; |
| | | } |
| | | |
| | | @Override |
| | | public String nativeNotify(HttpServletRequest request, HttpServletResponse response) { |
| | | Gson gson = new Gson(); |
| | | Map<String, String> map = new HashMap<>();//应答对象 |
| | | |
| | | try { |
| | | |
| | | //处理通知参数 |
| | | String body = HttpUtils.readData(request); |
| | | Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class); |
| | | String requestId = (String) bodyMap.get("id"); |
| | | logger.info("支付通知的id ===> {}", requestId); |
| | | logger.info("支付通知的完整数据 ===> {}", body); |
| | | //int a = 9 / 0; |
| | | |
| | | //签名的验证 |
| | | WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest |
| | | = new WechatPay2ValidatorForRequest(wxPayConfig.getVerifier(), requestId, body); |
| | | if (!wechatPay2ValidatorForRequest.validate(request)) { |
| | | |
| | | logger.error("通知验签失败"); |
| | | //失败应答 |
| | | response.setStatus(500); |
| | | map.put("code", "ERROR"); |
| | | map.put("message", "通知验签失败"); |
| | | return gson.toJson(map); |
| | | } |
| | | logger.info("通知验签成功"); |
| | | |
| | | //处理订单 |
| | | processOrder(bodyMap); |
| | | |
| | | //成功应答 |
| | | response.setStatus(200); |
| | | map.put("code", "SUCCESS"); |
| | | map.put("message", "成功"); |
| | | return gson.toJson(map); |
| | | |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | //失败应答 |
| | | response.setStatus(500); |
| | | map.put("code", "ERROR"); |
| | | map.put("message", "失败"); |
| | | return gson.toJson(map); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public String refundsNotify(HttpServletRequest request, HttpServletResponse response) { |
| | | Gson gson = new Gson(); |
| | | Map<String, String> map = new HashMap<>();//应答对象 |
| | | |
| | | try { |
| | | //处理通知参数 |
| | | String body = HttpUtils.readData(request); |
| | | Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class); |
| | | String requestId = (String) bodyMap.get("id"); |
| | | logger.info("支付通知的id ===> {}", requestId); |
| | | |
| | | //签名的验证 |
| | | WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest |
| | | = new WechatPay2ValidatorForRequest(wxPayConfig.getVerifier(), requestId, body); |
| | | if (!wechatPay2ValidatorForRequest.validate(request)) { |
| | | |
| | | logger.error("通知验签失败"); |
| | | //失败应答 |
| | | response.setStatus(500); |
| | | map.put("code", "ERROR"); |
| | | map.put("message", "通知验签失败"); |
| | | return gson.toJson(map); |
| | | } |
| | | logger.info("通知验签成功"); |
| | | |
| | | //处理退款单 |
| | | processRefund(bodyMap); |
| | | |
| | | //成功应答 |
| | | response.setStatus(200); |
| | | map.put("code", "SUCCESS"); |
| | | map.put("message", "成功"); |
| | | return gson.toJson(map); |
| | | |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | //失败应答 |
| | | response.setStatus(500); |
| | | map.put("code", "ERROR"); |
| | | map.put("message", "失败"); |
| | | return gson.toJson(map); |
| | | } |
| | | } |
| | | |
| | | |
| | | //创建签名所需要的参数xml格式 |
| | | // public static String createXML(Map<String, Object> map) { |
| | | // String xml = "<xml>"; |
| | | // Set<String> set = map.keySet(); |
| | | // Iterator<String> i = set.iterator(); |
| | | // while (i.hasNext()) { |
| | | // String str = i.next(); |
| | | // xml += "<" + str + ">" + "<![CDATA[" + map.get(str) + "]]>" + "</" + str + ">"; |
| | | // } |
| | | // xml += "</xml>"; |
| | | // return xml; |
| | | // } |
| | | |
| | | |
| | | } |