package com.ylx.web.controller.massage; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.ContentType; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.github.binarywang.wxpay.bean.notify.SignatureHeader; import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result; import com.github.binarywang.wxpay.exception.WxPayException; import com.github.binarywang.wxpay.service.WxPayService; import com.ijpay.core.IJPayHttpResponse; import com.ijpay.core.enums.RequestMethodEnum; import com.ijpay.core.kit.HttpKit; import com.ijpay.core.kit.PayKit; import com.ijpay.core.kit.WxPayKit; import com.ijpay.wxpay.WxPayApi; import com.ijpay.wxpay.enums.WxDomainEnum; import com.ijpay.wxpay.enums.v3.TransferApiEnum; import com.ijpay.wxpay.model.v3.BatchTransferModel; import com.ijpay.wxpay.model.v3.TransferDetailInput; import com.ylx.common.config.WxPayConfig; import com.ylx.common.core.domain.R; import com.ylx.common.weixinPay.enums.WxPayTypeEnum; import com.ylx.giftCard.domain.GiftCardOrder; import com.ylx.giftCard.enums.GiftCardOrderStatusEnum; import com.ylx.giftCard.service.IGiftCardOrderService; import com.ylx.massage.domain.TRecharge; import com.ylx.massage.domain.TWxUser; import com.ylx.massage.enums.BillTypeEnum; import com.ylx.massage.service.RefundVoucherService; import com.ylx.massage.service.TRechargeService; import com.ylx.massage.service.TWxUserService; import com.ylx.order.domain.TOrder; import com.ylx.order.enums.OrderStatusEnum; import com.ylx.order.service.TOrderService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.security.cert.X509Certificate; import java.util.Collections; import java.util.HashMap; import java.util.Map; import static com.ylx.common.constant.HttpStatus.SUCCESS; /** * @author jianlong * @date 2024-04-03 15:27 */ @RestController @Slf4j @RequestMapping("/wx/pay") @Api(tags = {"微信支付"}) public class PayController { @Resource private WxPayConfig wxPayProperties; @Resource private TRechargeService rechargeService; @Resource private RefundVoucherService refundVoucherService; @Resource private TWxUserService wxUserService; String serialNo; @Resource private WxPayService wxPayService; @Resource private IGiftCardOrderService giftCardOrderService; @Resource private TOrderService orderService; /** * 小程序微信支付的第一步,统一下单 */ @PostMapping("/pay") @ApiOperation("AIPV3微信支付充值") public R createUnifiedOrder(@RequestBody TRecharge recharge) throws Exception { TRecharge rechargeResp = rechargeService.recharge(recharge); return rechargeService.getPay(rechargeResp.getRechargeNo(), recharge.getdMoney(), recharge.getcOpenId(), BillTypeEnum.RECHARGE.getInfo(), BillTypeEnum.RECHARGE.getCode().toString()); } /** * 获取商户API证书序列号 * * @return String 证书序列号 */ private String getSerialNumber() { if (StrUtil.isEmpty(serialNo)) { // 获取证书序列号 X509Certificate certificate = PayKit.getCertificate(wxPayProperties.getCertPath()); if (null != certificate) { serialNo = certificate.getSerialNumber().toString(16).toUpperCase(); // 提前两天检查证书是否有效 boolean isValid = PayKit.checkCertificateIsValid(certificate, wxPayProperties.getMchId(), -2); log.info("证书是否可用 {} 证书有效期为 {}", isValid, DateUtil.format(certificate.getNotAfter(), DatePattern.NORM_DATETIME_PATTERN)); } } System.out.println("serialNo:" + serialNo); return serialNo; } // /** // * 微信支付回调接口 // * // * @param request // * @param response // */ // @RequestMapping(value = "/payNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET}) // @ResponseBody // @ApiOperation("微信支付回调接口") // public void payNotify(HttpServletRequest request, HttpServletResponse response) { // log.info("微信支付回调接口====================================>>>>微信支付回调接口"); // Map map = new HashMap<>(12); // try { // String timestamp = request.getHeader("Wechatpay-Timestamp"); // String nonce = request.getHeader("Wechatpay-Nonce"); // String serialNo = request.getHeader("Wechatpay-Serial"); // String signature = request.getHeader("Wechatpay-Signature"); // // log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature); // String result = HttpKit.readData(request); // log.info("支付通知密文 {}", result); // // // 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号 // String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp, // wxPayProperties.getMchKey(), wxPayProperties.getPlatFormPath()); // // log.info("支付通知明文 {}", plainText); // // if (StrUtil.isNotEmpty(plainText)) { // response.setStatus(200); // map.put("code", "SUCCESS"); // map.put("message", "SUCCESS"); // // 处理业务逻辑 // JSONObject jsonObject = new JSONObject(plainText); // if (jsonObject.get("attach").equals(BillTypeEnum.WX_PAY.getCode().toString())) { // // 服务订单支付成功 // orderService.payNotifyOrder(jsonObject.get("out_trade_no").toString()); // } else if (jsonObject.get("attach").equals(PayTypeEnum.WX_PAY.getCode().toString())) { // // 商品订单支付成功 // String productOrderNo = jsonObject.get("out_trade_no").toString(); // log.info("商品订单支付回调开始处理,订单号:{}", productOrderNo); // productOrderInfoService.handleWxPayCallback(productOrderNo); // log.info("商品订单支付回调处理完成,订单号:{}", productOrderNo); // } else { // TRecharge outTradeNo = rechargeService.increaseAmount(jsonObject.get("out_trade_no").toString()); // } // } else { // response.setStatus(500); // map.put("code", "ERROR"); // map.put("message", "签名错误"); // } // response.setHeader("Content-type", ContentType.JSON.toString()); // response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8)); // response.flushBuffer(); // } catch (Exception e) { // log.error("系统异常", e); // } // } @PostMapping("/payNotify") public Map handlePayNotify(@RequestBody String notifyData, @RequestHeader("Wechatpay-Signature") SignatureHeader signature) { Map resp = new HashMap<>(); try { // 1. SDK验签+解密报文 WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayService.parseOrderNotifyV3Result(notifyData, signature).getResult(); String outTradeNo = result.getOutTradeNo(); String tradeState = result.getTradeState(); log.info("微信V3回调解密成功,商户订单号:{},微信单号:{},交易状态:{}", outTradeNo, result.getTransactionId(), tradeState); // 2. 仅SUCCESS才处理订单 if ("SUCCESS".equals(tradeState)) { // 2. 获取我们在下单时传入的 attach String attach = result.getAttach(); String openid = result.getPayer().getOpenid(); if (StrUtil.isEmpty(openid)) { log.error("openid不存在"); resp.put("code", "FAIL"); resp.put("message", "openid不存在"); return resp; } log.info("支付成功用户openId,openId: {}", openid); TWxUser wxUser = wxUserService.getByOpenId(openid); if (ObjectUtil.isNull(wxUser)) { log.error("支付成功用户不存在,openId: {}", openid); resp.put("code", "FAIL"); resp.put("message", "支付成功用户不存在"); return resp; } // 3. 根据 attach 判断商品类型并进行不同的业务处理 if (WxPayTypeEnum.GIFT_CARD.getCode().equals(attach)) { log.info("检测到购物卡支付成功,订单号: {}", outTradeNo); // 3.1 更新订单支付状态 LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(GiftCardOrder::getOrderNo, outTradeNo); GiftCardOrder cardOrder = this.giftCardOrderService.getOne(wrapper); if (ObjectUtil.isNull(cardOrder)) { log.error("订单不存在,订单号: {}", outTradeNo); resp.put("code", "FAIL"); resp.put("message", "订单不存在"); return resp; } // 3.2 检查是否已处理 if (ObjectUtil.equals(GiftCardOrderStatusEnum.PAID.getCode(), cardOrder.getStatus())) { log.warn("订单已处理过:{}", outTradeNo); resp.put("code", "SUCCESS"); resp.put("message", "OK"); return resp; } // 3.3 处理订单相关数据 this.giftCardOrderService.processGiftCardPayment(result, wxUser, cardOrder); } else if (WxPayTypeEnum.EMOTION_GOODS.getCode().equals(attach)) { log.info("检测到情感服务商品支付成功,订单号: {}", outTradeNo); // 3.1 更新订单支付状态 LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(TOrder::getOrderNo, outTradeNo); TOrder order = this.orderService.getOne(wrapper); if (ObjectUtil.isNull(order)) { log.error("订单不存在,订单号: {}", outTradeNo); resp.put("code", "FAIL"); resp.put("message", "订单不存在"); return resp; } // 3.2 检查是否已处理 if (ObjectUtil.equals(OrderStatusEnum.PENDING_DISPATCH.getCode(), order.getStatus())) { log.warn("订单已处理过:{}", outTradeNo); resp.put("code", "SUCCESS"); resp.put("message", "OK"); return resp; } // 3.3 处理订单相关数据 this.orderService.processOrderPayment(result, wxUser, order); } } //3. 返回成功 resp.put("code", "SUCCESS"); resp.put("message", "OK"); } catch (WxPayException e) { log.error("微信支付回调异常:{}", e.getMessage(), e); resp.put("code", "FAIL"); resp.put("message", e.getMessage()); } catch (Exception e) { log.error("支付回调处理异常:{}", e.getMessage(), e); resp.put("code", "FAIL"); resp.put("message", "系统异常"); } return resp; } /** * 微信批量提现 * * @param openId * @return String */ @RequestMapping("/batchTransfer") @ApiOperation("微信批量提现") @ResponseBody public String batchTransfer(@RequestParam(value = "openId", required = false, defaultValue = "o-_-itxuXeGW3O1cxJ7FXNmq8Wf8") String openId) { try { BatchTransferModel batchTransferModel = new BatchTransferModel() .setAppid(wxPayProperties.getAppId()) .setOut_batch_no(PayKit.generateStr()) .setBatch_name("IJPay 测试微信转账到零钱") .setBatch_remark("IJPay 测试微信转账到零钱") .setTotal_amount(1) .setTotal_num(1) .setTransfer_detail_list(Collections.singletonList( new TransferDetailInput() .setOut_detail_no(PayKit.generateStr()) .setTransfer_amount(1) .setTransfer_remark("IJPay 测试微信转账到零钱") .setOpenid(openId))); log.info("发起商家转账请求参数 {}", JSONUtil.toJsonStr(batchTransferModel)); IJPayHttpResponse response = WxPayApi.v3( RequestMethodEnum.POST, WxDomainEnum.CHINA.toString(), TransferApiEnum.TRANSFER_BATCHES.toString(), wxPayProperties.getMchId(), getSerialNumber(), null, wxPayProperties.getCertKeyPath(), JSONUtil.toJsonStr(batchTransferModel) ); log.info("发起商家转账响应 {}", response); // 根据证书序列号查询对应的证书来验证签名结果 boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath()); log.info("verifySignature: {}", verifySignature); if (response.getStatus() == SUCCESS && verifySignature) { return response.getBody(); } return JSONUtil.toJsonStr(response); } catch (Exception e) { log.error("系统异常", e); return e.getMessage(); } } /** * 退款 * * @param outRefundNo 退款订单号 * @param amount 退款金额 * @param transactionId 微信支付订单号 * @param outTradeNo 商户订单号 * @return String 退款结果 */ @RequestMapping("/refund") @ResponseBody public String refund(@RequestParam(required = false) String outRefundNo, @RequestParam(required = false) BigDecimal amount, @RequestParam(required = false) String transactionId, @RequestParam(required = false) String outTradeNo) { try { return rechargeService.refund(outRefundNo, transactionId, outTradeNo, amount); } catch (Exception e) { log.error("退款异常", e); throw new RuntimeException(e); } } /** * 微信退款回调接口 * * @param request * @param response */ @ApiOperation("微信退款回调接口") @RequestMapping(value = "/refundNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET}) public void refundWechatCallback(HttpServletRequest request, HttpServletResponse response) { log.info("微信退款回调接口====================================>>>>微信退款回调接口"); Map map = new HashMap<>(12); try { String timestamp = request.getHeader("Wechatpay-Timestamp"); String nonce = request.getHeader("Wechatpay-Nonce"); String serialNo = request.getHeader("Wechatpay-Serial"); String signature = request.getHeader("Wechatpay-Signature"); log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature); String result = HttpKit.readData(request); log.info("退款通知密文 {}", result); // 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号 String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp, wxPayProperties.getMchKey(), wxPayProperties.getPlatFormPath()); log.info("退款通知明文 {}", plainText); if (StrUtil.isNotEmpty(plainText)) { response.setStatus(200); map.put("code", "SUCCESS"); map.put("message", "SUCCESS"); // 处理业务逻辑 JSONObject jsonObject = new JSONObject(plainText); //退款单号 String refundNo = jsonObject.get("out_refund_no").toString(); refundVoucherService.refundWechatCallback(refundNo); } else { response.setStatus(500); map.put("code", "ERROR"); map.put("message", "签名错误"); } response.setHeader("Content-type", ContentType.JSON.toString()); response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8)); response.flushBuffer(); } catch (Exception e) { log.error("系统异常", e); } } /** * 根据商户订单号查询微信支付订单 * * @param outTradeNo 商户订单号 * @return R 订单详情 */ @GetMapping("/query/order/{outTradeNo}") @ApiOperation("根据商户订单号查询微信支付订单") public R queryOrderByOutTradeNo(@PathVariable("outTradeNo") String outTradeNo) { try { log.info("查询微信支付订单,商户订单号:{}", outTradeNo); // V3 API:根据商户订单号查询订单接口路径 // GET /v3/pay/transactions/out-trade-no/{out_trade_no} String queryUrl = String.format("/v3/pay/transactions/out-trade-no/%s", outTradeNo); log.info("查询订单URL:{}", queryUrl); Map queryParams = new HashMap<>(); queryParams.put("mchid", wxPayProperties.getMchId()); // 调用微信支付V3接口查询订单 IJPayHttpResponse response = WxPayApi.v3( RequestMethodEnum.GET, WxDomainEnum.CHINA.toString(), queryUrl, wxPayProperties.getMchId(), getSerialNumber(), null, wxPayProperties.getCertKeyPath(), queryParams ); log.info("查询订单响应状态:{},响应体:{}", response.getStatus(), response.getBody()); // 处理响应 if (response.getStatus() == SUCCESS) { // 验证响应签名 boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath()); log.info("响应签名验证结果:{}", verifySignature); if (verifySignature) { log.info("商户订单号 {} 查询成功,订单信息:{}", outTradeNo, response.getBody()); JSONObject result = JSONUtil.parseObj(response.getBody()); return R.ok(result); } else { log.error("商户订单号 {} 查询响应签名验证失败", outTradeNo); return R.fail("查询订单响应签名验证失败"); } } else { log.error("商户订单号 {} 查询失败,状态码:{},响应:{}", outTradeNo, response.getStatus(), response.getBody()); return R.fail("查询订单失败:" + response.getBody()); } } catch (Exception e) { log.error("查询微信支付订单异常,商户订单号:{},错误:{}", outTradeNo, e.getMessage(), e); return R.fail("查询订单异常:" + e.getMessage()); } } }