package org.ruoyi.system.controller.system; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.extra.qrcode.QrCodeUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse; import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult; import com.github.binarywang.wxpay.bean.request.BaseWxPayRequest; import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; import com.github.binarywang.wxpay.exception.WxPayException; import com.github.binarywang.wxpay.service.WxPayService; import com.stripe.Stripe; import com.stripe.exception.StripeException; import com.stripe.model.Event; import com.stripe.model.Price; import com.stripe.model.Product; import com.stripe.model.checkout.Session; import com.stripe.net.Webhook; import com.stripe.param.checkout.SessionCreateParams; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ruoyi.common.config.PayConfig; import org.ruoyi.common.core.domain.R; import org.ruoyi.common.core.exception.base.BaseException; import org.ruoyi.common.core.service.ConfigService; import org.ruoyi.common.core.utils.StringUtils; import org.ruoyi.common.oss.core.OssClient; import org.ruoyi.common.oss.entity.UploadResult; import org.ruoyi.common.oss.factory.OssFactory; import org.ruoyi.common.response.PayResponse; import org.ruoyi.common.service.PayService; import org.ruoyi.common.utils.MD5Util; import org.ruoyi.system.domain.bo.PaymentOrdersBo; import org.ruoyi.system.domain.bo.SysUserBo; import org.ruoyi.system.domain.request.OrderRequest; import org.ruoyi.system.domain.vo.PaymentOrdersVo; import org.ruoyi.system.domain.vo.SysUserVo; import org.ruoyi.system.service.IPaymentOrdersService; import org.ruoyi.system.service.ISysUserService; import org.springframework.web.bind.annotation.*; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.HashMap; import java.util.List; import java.util.Map; @RequiredArgsConstructor @RestController @RequestMapping("/pay") @Slf4j public class PayController { private final PayService payService; private final ISysUserService userService; private final IPaymentOrdersService paymentOrdersService; private final PayConfig payConfig; private final WxPayService wxService; private final ConfigService configService; /** * 获取支付二维码 * * @Date 2023/7/3 * @return void **/ @PostMapping("/payUrl") public R payUrl(@RequestBody OrderRequest orderRequest) { PaymentOrdersBo payOrder = paymentOrdersService.createPayOrder(orderRequest); PaymentOrdersVo paymentOrdersVo = new PaymentOrdersVo(); if(!Boolean.parseBoolean(getKey("enabled"))){ String payUrl = payService.getPayUrl(payOrder.getOrderNo(), orderRequest.getName(), Double.parseDouble(orderRequest.getMoney()), "192.168.1.6"); byte[] bytes = QrCodeUtil.generatePng(payUrl, 300, 300); OssClient storage = OssFactory.instance(); UploadResult upload=storage.upload(bytes, storage.getPath("qrCode",".png"), "image/png"); BeanUtil.copyProperties(payOrder,paymentOrdersVo); paymentOrdersVo.setUrl(upload.getUrl()); }else { WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest(); request.setTradeType("NATIVE"); request.setBody(orderRequest.getName()); request.setOutTradeNo(payOrder.getOrderNo()); request.setTotalFee(BaseWxPayRequest.yuanToFen(orderRequest.getMoney())); request.setSpbillCreateIp("127.0.0.1"); request.setNotifyUrl(getKey("notifyUrl")); request.setProductId(payOrder.getId().toString()); try { WxPayNativeOrderResult order = wxService.createOrder(request); byte[] bytes = QrCodeUtil.generatePng(order.getCodeUrl(), 300, 300); OssClient storage = OssFactory.instance(); UploadResult upload = storage.upload(bytes, storage.getPath("qrCode",".png"), "image/png"); BeanUtil.copyProperties(payOrder,paymentOrdersVo); paymentOrdersVo.setUrl(upload.getUrl()); } catch (WxPayException e) { throw new BaseException("获取微信支付二维码发生错误:{}"+e.getMessage()); } } return R.ok(paymentOrdersVo); } /** * 回调通知地址 * * @Date 2023/7/3 * @param * @return void **/ @GetMapping("/notifyUrl") public String notifyUrl(PayResponse payResponse) { // 校验签名 String mdString = "money=" + payResponse.getMoney() + "&name=" + payResponse.getName() + "&out_trade_no=" + payResponse.getOut_trade_no() + "&pid=" + payConfig.getPid() + "&trade_no=" + payResponse.getTrade_no() + "&trade_status=" + payResponse.getTrade_status() + "&type=" + payResponse.getType() + payConfig.getKey(); String sign = MD5Util.GetMD5Code(mdString); if(!sign.equals(payResponse.getSign())){ throw new BaseException("校验签名失败!"); } double money = Double.parseDouble(payResponse.getMoney()); log.info("支付订单号{}",payResponse); PaymentOrdersBo paymentOrdersBo = new PaymentOrdersBo(); paymentOrdersBo.setOrderNo(payResponse.getOut_trade_no()); List paymentOrdersList = paymentOrdersService.queryList(paymentOrdersBo); if (CollectionUtil.isEmpty(paymentOrdersList)){ throw new BaseException("订单不存在!"); } // 订单状态修改为已支付 PaymentOrdersVo paymentOrdersVo = paymentOrdersList.get(0); paymentOrdersVo.setPaymentStatus("2"); paymentOrdersVo.setPaymentMethod(payResponse.getType()); BeanUtil.copyProperties(paymentOrdersVo,paymentOrdersBo); paymentOrdersService.updateByBo(paymentOrdersBo); SysUserVo sysUserVo = userService.selectUserById(paymentOrdersVo.getUserId()); sysUserVo.setUserBalance(sysUserVo.getUserBalance() + money); SysUserBo sysUserBo = new SysUserBo(); BeanUtil.copyProperties(sysUserVo,sysUserBo); // 设置为付费用户 sysUserBo.setUserGrade("1"); userService.updateUser(sysUserBo); return "success"; } /** * 跳转通知地址 * * @Date 2023/7/3 * @param * @return void **/ @GetMapping("/return_url") public void returnUrl() { log.info("return_url==========="); } @PostMapping("/notify/wxOrder") public String parseOrderNotifyResult(@RequestBody String xmlData) throws WxPayException { WxPayOrderNotifyResult notifyResult = this.wxService.parseOrderNotifyResult(xmlData); // TODO 根据自己业务场景需要构造返回对象 PaymentOrdersBo paymentOrdersBo = new PaymentOrdersBo(); paymentOrdersBo.setOrderNo(notifyResult.getOutTradeNo()); List paymentOrdersList = paymentOrdersService.queryList(paymentOrdersBo); PaymentOrdersVo paymentOrdersVo = paymentOrdersList.get(0); paymentOrdersVo.setPaymentStatus("2"); paymentOrdersVo.setPaymentMethod("wx"); BeanUtil.copyProperties(paymentOrdersVo,paymentOrdersBo); paymentOrdersService.updateByBo(paymentOrdersBo); SysUserVo sysUserVo = userService.selectUserById(paymentOrdersVo.getUserId()); sysUserVo.setUserBalance(sysUserVo.getUserBalance() + convertCentsToYuan(notifyResult.getTotalFee())); SysUserBo sysUserBo = new SysUserBo(); BeanUtil.copyProperties(sysUserVo,sysUserBo); // 设置为付费用户 sysUserBo.setUserGrade("1"); userService.updateUser(sysUserBo); return WxPayNotifyResponse.success("success"); } /** * 将分转换为元,并保留精度。 * * @param cents 分的金额,类型为Integer * @return 转换后的元金额,类型为double */ public static double convertCentsToYuan(Integer cents) { // 处理空输入 if (cents == null) { throw new IllegalArgumentException("输入的分金额不能为空"); } // 100分 = 1元 BigDecimal centsBigDecimal = new BigDecimal(cents); BigDecimal yuan = centsBigDecimal.divide(new BigDecimal(100), 2, RoundingMode.HALF_UP); // 转换为double并返回 return yuan.doubleValue(); } /** * 获取订单信息 * */ @PostMapping("/orderInfo") public R orderInfo(@RequestBody OrderRequest orderRequest) { if(StringUtils.isEmpty(orderRequest.getOrderNo())){ throw new BaseException("订单号不能为空!"); } PaymentOrdersBo paymentOrdersBo = new PaymentOrdersBo(); paymentOrdersBo.setOrderNo(orderRequest.getOrderNo()); List paymentOrdersList = paymentOrdersService.queryList(paymentOrdersBo); if (CollectionUtil.isEmpty(paymentOrdersList)){ throw new BaseException("订单不存在!"); } PaymentOrdersVo paymentOrdersVo = paymentOrdersList.get(0); return R.ok(paymentOrdersVo); } // 获取支付链接 // static { // Stripe.apiKey = "sk_test_51PMMj2KcfX4oNioqXkoKpScTsgmR55xQki2tg8MEZJYc0gjhYV85t2FzDasE06eqZb0sqyYhOp3UXhcGGQLWI4A9008aq8SOnb"; // } /** * 去支付 * 1、创建产品 * 2、设置价格 * 3、创建支付信息 得到url * @return */ @PostMapping("/stripePay") public String pay(@RequestBody OrderRequest orderRequest) throws StripeException { String enabled = configService.getConfigValue("stripe", "enabled"); if(!Boolean.parseBoolean(enabled)){ String prompt = configService.getConfigValue("stripe", "prompt"); throw new BaseException(prompt); } // 获取支付链接 Stripe.apiKey = configService.getConfigValue("stripe", "key"); // 获取金额字符串并解析为 double double moneyDouble = Double.parseDouble(orderRequest.getMoney()); // 将金额转换为以分为单位的整数 int randMoney = (int) (moneyDouble * 100); Map params = new HashMap<>(); params.put("name", orderRequest.getName()); Product product = Product.create(params); Map recurring = new HashMap<>(); recurring.put("interval", "month"); Map params2 = new HashMap<>(); params2.put("unit_amount", randMoney); params2.put("currency", "usd"); params2.put("recurring", recurring); params2.put("product", product.getId()); Price price = Price.create(params2); // 创建支付订单 PaymentOrdersBo payOrder = paymentOrdersService.createPayOrder(orderRequest); //创建支付信息 得到url SessionCreateParams params3 = SessionCreateParams.builder() .setMode(SessionCreateParams.Mode.SUBSCRIPTION) .setSuccessUrl(configService.getConfigValue("stripe", "success")) .setCancelUrl(configService.getConfigValue("stripe", "cancel")) .addLineItem( SessionCreateParams.LineItem.builder() .setQuantity(1L) .setPrice(price.getId()) .build()).putMetadata("orderId", payOrder.getOrderNo()) .build(); Session session = Session.create(params3); return session.getUrl(); } /** * 支付回调 * */ @PostMapping("/stripe_events") public R stripeEvent(HttpServletRequest request) { try { String endpointSecret = configService.getConfigValue("stripe", "secret");//webhook秘钥签名 InputStream inputStream = request.getInputStream(); ByteArrayOutputStream output = new ByteArrayOutputStream(); byte[] buffer = new byte[1024*4]; int n = 0; while (-1 != (n = inputStream.read(buffer))) { output.write(buffer, 0, n); } byte[] bytes = output.toByteArray(); String payload = new String(bytes, "UTF-8"); String sigHeader = request.getHeader("Stripe-Signature"); Event event = Webhook.constructEvent(payload, sigHeader, endpointSecret);//验签,并获取事件 if("checkout.session.completed".equals(event.getType())){ // 解析 JSON 字符串为 JSONObject JSONObject jsonObject = JSONUtil.parseObj(event); // 获取 metadata 对象 JSONObject metadata = jsonObject.getJSONObject("data") .getJSONObject("object") .getJSONObject("metadata"); OrderRequest orderRequest = new OrderRequest(); orderRequest.setPayType("stripe"); orderRequest.setOrderNo(metadata.getStr("orderId")); paymentOrdersService.updatePayOrder(orderRequest); } } catch (Exception e) { System.out.println("stripe异步通知(webhook事件)"+e); } return R.ok(); } public String getKey(String key) { return configService.getConfigValue("weixin", key); } }