diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index fc47e9b..06f49cf 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -255,10 +255,19 @@ management: --- # mail 邮件发送 mail: enabled: false -agora: - app-id: 13e7c8066b9a4b2488eeffd66a9ab29c - key: 9087bf18d142410ea113b60e8fbed8c9 - secret: f8c048e0b2f247409b42395efb846cba· yunxin: app-key: 748496374b04344194c68b53e411e09c123 app-secret: 77c09917d2de +wx: + pay: + app-id: wxefe4087f9c1e0fd7 + mch-id: 1673939172 + partner-key: dhsajidua9sd8sahdj1h289dhjkashd8 + notify-url: "http://xq.mubai8888.com" + mp: + app-id: wxefe4087f9c1e0fd7 + secret: f11d09d4024c0ca18e895eabe99df549 + aes-key: + token: + config-storage: + type: RedisTemplate diff --git a/ruoyi-xq/pom.xml b/ruoyi-xq/pom.xml index 462f906..a81a31e 100644 --- a/ruoyi-xq/pom.xml +++ b/ruoyi-xq/pom.xml @@ -63,5 +63,10 @@ javase 3.4.1 + + com.github.binarywang + wx-java-mp-spring-boot-starter + 4.4.0 + diff --git a/ruoyi-xq/src/main/java/com/ruoyi/xq/config/WxPayProperties.java b/ruoyi-xq/src/main/java/com/ruoyi/xq/config/WxPayProperties.java new file mode 100644 index 0000000..a6dca1f --- /dev/null +++ b/ruoyi-xq/src/main/java/com/ruoyi/xq/config/WxPayProperties.java @@ -0,0 +1,33 @@ +package com.ruoyi.xq.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.io.Serializable; + +@Data +@Component +@ConfigurationProperties(prefix = "wx.pay") +public class WxPayProperties implements Serializable { + + private static final long serialVersionUID=1L; + + /** + * + */ + private String appId; + /** + * + */ + private String mchId; + /** + * + */ + private String partnerKey; + /** + * + */ + private String notifyUrl; + +} diff --git a/ruoyi-xq/src/main/java/com/ruoyi/xq/controller/app/MpController.java b/ruoyi-xq/src/main/java/com/ruoyi/xq/controller/app/MpController.java new file mode 100644 index 0000000..e8bd017 --- /dev/null +++ b/ruoyi-xq/src/main/java/com/ruoyi/xq/controller/app/MpController.java @@ -0,0 +1,50 @@ +package com.ruoyi.xq.controller.app; + +import com.ruoyi.common.core.domain.R; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.bean.WxJsapiSignature; +import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/mp") +@Tag(name = "公众号相关接口") +@Validated +@Slf4j +public class MpController { + + @Autowired + private WxMpService wxMpService; + + @Operation(summary = "公众号静默获取基础数据") + @GetMapping("/auth/access_token") + public R getAccessToken(@RequestParam(value = "code") String code) { + try { + return R.ok(wxMpService.getOAuth2Service().getAccessToken(code)); + } catch (WxErrorException e) { + log.error("error", e); + return R.fail("获取失败"); + } + } + + @Operation(summary = "jsapi的授权") + @GetMapping("/jsapi/token") + public R jsApiToken(@RequestParam(value = "url") String url) { + try { + WxJsapiSignature jsapiSignature = wxMpService.createJsapiSignature(url); + return R.fail(jsapiSignature); + } catch (WxErrorException e) { + log.error("error", e); + return R.fail("获取失败"); + } + } +} diff --git a/ruoyi-xq/src/main/java/com/ruoyi/xq/controller/app/PayController.java b/ruoyi-xq/src/main/java/com/ruoyi/xq/controller/app/PayController.java index 38fd87d..92293c9 100644 --- a/ruoyi-xq/src/main/java/com/ruoyi/xq/controller/app/PayController.java +++ b/ruoyi-xq/src/main/java/com/ruoyi/xq/controller/app/PayController.java @@ -1,11 +1,179 @@ package com.ruoyi.xq.controller.app; +import cn.dev33.satoken.annotation.SaIgnore; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSON; +import com.ijpay.core.enums.SignType; +import com.ijpay.core.enums.TradeType; +import com.ijpay.core.kit.HttpKit; +import com.ijpay.core.kit.WxPayKit; +import com.ijpay.wxpay.WxPayApi; +import com.ijpay.wxpay.model.UnifiedOrderModel; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.domain.R; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.xq.config.WxPayProperties; +import com.ruoyi.xq.dto.app.pay.PayControllerDTO; +import com.ruoyi.xq.dto.app.pay.PayOrderInfoDTO; +import com.ruoyi.xq.dto.app.pay.PayReturnResp; +import com.ruoyi.xq.enums.pay.PlatformTypeEnum; +import com.ruoyi.xq.manager.PayManager; +import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; + @RestController @RequestMapping("/api/pay") -@Tag(name = "支付接口") +@Tag(name = "支付相关的接口") +@Validated +@Slf4j public class PayController { + + @Autowired + private WxPayProperties wxPayProperties; + @Autowired + private PayManager payManager; + + private static final String NOTIFY_WX_URL = "/api/pay/wx/notify"; + private static final String NOTIFY_ALI_URL = "/api/pay/ali/notify"; + + @PostMapping(value = "/jsapi/wx/test") + @Operation(summary = "微信支付-公众号-测试") + @Log(title = "微信支付-公众号-测试", businessType = BusinessType.OTHER, isSaveDb = true) + public R jsapiPayTest(@RequestBody PayControllerDTO payController){ + String openId = payController.getOpenId(); + if (StrUtil.isEmpty(openId)) { + return R.fail("openid is null"); + } + String ip = ServletUtils.getClientIP(); + Map params = UnifiedOrderModel + .builder() + .appid(wxPayProperties.getAppId()) + .mch_id(wxPayProperties.getMchId()) + .nonce_str(WxPayKit.generateStr()) + .body("测试发起支付") + .out_trade_no(WxPayKit.generateStr()) + .total_fee("1000") + .spbill_create_ip(ip) + .notify_url(wxPayProperties.getNotifyUrl()+NOTIFY_WX_URL) + .trade_type(TradeType.JSAPI.getTradeType()) + .openid(openId) + .build() + .createSign(wxPayProperties.getPartnerKey(), SignType.HMACSHA256); + String xmlResult = WxPayApi.pushOrder(false, params); + log.info(xmlResult); + Map resultMap = WxPayKit.xmlToMap(xmlResult); + String returnCode = resultMap.get("return_code"); + String returnMsg = resultMap.get("return_msg"); + if (!WxPayKit.codeIsOk(returnCode)) { + log.error("j微信支付失败 returnMsg={}",returnMsg); + return R.fail("微信支付失败,请联系客服"); + } + String resultCode = resultMap.get("result_code"); + if (!WxPayKit.codeIsOk(resultCode)) { + log.error("j微信支付失败 returnMsg={}",returnMsg); + return R.fail("微信支付失败,请联系客服"); + } + // 以下字段在 return_code 和 result_code 都为 SUCCESS 的时候有返回 + String prepayId = resultMap.get("prepay_id"); + Map packageParams = WxPayKit.prepayIdCreateSign(prepayId, wxPayProperties.getAppId(), + wxPayProperties.getPartnerKey(), SignType.HMACSHA256); + String jsonStr = JSON.toJSONString(packageParams); + PayReturnResp resp = new PayReturnResp(); + resp.setData(jsonStr); + return R.ok(resp); + } + + @PostMapping(value = "/jsapi/wx") + @Operation(summary = "微信支付-公众号") + @Log(title = "微信支付-公众号", businessType = BusinessType.OTHER, isSaveDb = true) + public R jsapiPay(@RequestBody PayControllerDTO payController){ + PayOrderInfoDTO payOrderInfo = payManager.getOrderInfo(payController.getOrderNo()); + String openId = payController.getOpenId(); + if (StrUtil.isEmpty(openId)) { + return R.fail("openid is null"); + } + String ip = ServletUtils.getClientIP(); + Map params = UnifiedOrderModel + .builder() + .appid(wxPayProperties.getAppId()) + .mch_id(wxPayProperties.getMchId()) + .nonce_str(WxPayKit.generateStr()) + .body(payOrderInfo.getBody()) + .out_trade_no(WxPayKit.generateStr()) + .total_fee(payOrderInfo.getPriceFenStr()) + .spbill_create_ip(ip) + .notify_url(wxPayProperties.getNotifyUrl()+NOTIFY_WX_URL) + .trade_type(TradeType.JSAPI.getTradeType()) + .openid(openId) + .build() + .createSign(wxPayProperties.getPartnerKey(), SignType.HMACSHA256); + String xmlResult = WxPayApi.pushOrder(false, params); + log.info(xmlResult); + Map resultMap = WxPayKit.xmlToMap(xmlResult); + String returnCode = resultMap.get("return_code"); + String returnMsg = resultMap.get("return_msg"); + if (!WxPayKit.codeIsOk(returnCode)) { + log.error("j微信支付失败 returnMsg={}",returnMsg); + return R.fail("微信支付失败,请联系客服"); + } + String resultCode = resultMap.get("result_code"); + if (!WxPayKit.codeIsOk(resultCode)) { + log.error("j微信支付失败 returnMsg={}",returnMsg); + return R.fail("微信支付失败,请联系客服"); + } + // 以下字段在 return_code 和 result_code 都为 SUCCESS 的时候有返回 + String prepayId = resultMap.get("prepay_id"); + Map packageParams = WxPayKit.prepayIdCreateSign(prepayId, wxPayProperties.getAppId(), + wxPayProperties.getPartnerKey(), SignType.HMACSHA256); + String jsonStr = JSON.toJSONString(packageParams); + PayReturnResp resp = new PayReturnResp(); + resp.setData(jsonStr); + return R.ok(resp); + } + + + @PostMapping(value = "/wx/notify") + @Operation(hidden = true) + @Log(title = "微信回调", businessType = BusinessType.OTHER, isSaveDb = false) + @SaIgnore + public String payNotify(HttpServletRequest request) { + String xmlMsg = HttpKit.readData(request); + log.info("支付通知=" + xmlMsg); + Map params = WxPayKit.xmlToMap(xmlMsg); + String returnCode = params.get("return_code"); + String mchId = params.get("mch_id"); + if(!wxPayProperties.getMchId().equals(mchId)){ + log.error("未找到微信配置 mchId={}",mchId); + return null; + } + // 注意重复通知的情况,同一订单号可能收到多次通知,请注意一定先判断订单状态 + // 注意此处签名方式需与统一下单的签名类型一致 + if (WxPayKit.verifyNotify(params, wxPayProperties.getPartnerKey(), SignType.MD5)) { + if (WxPayKit.codeIsOk(returnCode)) { + String outTradeNo = params.get("out_trade_no"); + String transactionId = params.get("transaction_id"); + payManager.callBack(outTradeNo,params,mchId, PlatformTypeEnum.WX); + Map xml = new HashMap<>(2); + xml.put("return_code", "SUCCESS"); + xml.put("return_msg", "OK"); + return WxPayKit.toXml(xml); + } + } + return null; + } + + + } diff --git a/ruoyi-xq/src/main/java/com/ruoyi/xq/domain/VipOrder.java b/ruoyi-xq/src/main/java/com/ruoyi/xq/domain/VipOrder.java index 9ab377a..edbcc42 100644 --- a/ruoyi-xq/src/main/java/com/ruoyi/xq/domain/VipOrder.java +++ b/ruoyi-xq/src/main/java/com/ruoyi/xq/domain/VipOrder.java @@ -54,6 +54,7 @@ public class VipOrder implements Serializable { * 会员价格 */ private BigDecimal vipPrice; + private String body; /** * 订单号 */ diff --git a/ruoyi-xq/src/main/java/com/ruoyi/xq/domain/WxTransOrder.java b/ruoyi-xq/src/main/java/com/ruoyi/xq/domain/WxTransOrder.java index e575559..a87bda1 100644 --- a/ruoyi-xq/src/main/java/com/ruoyi/xq/domain/WxTransOrder.java +++ b/ruoyi-xq/src/main/java/com/ruoyi/xq/domain/WxTransOrder.java @@ -42,6 +42,8 @@ public class WxTransOrder implements Serializable { * 跟踪ID */ private String traceId; + + private String body; /** * WX-ID */ diff --git a/ruoyi-xq/src/main/java/com/ruoyi/xq/dto/app/pay/PayControllerDTO.java b/ruoyi-xq/src/main/java/com/ruoyi/xq/dto/app/pay/PayControllerDTO.java new file mode 100644 index 0000000..85ffda3 --- /dev/null +++ b/ruoyi-xq/src/main/java/com/ruoyi/xq/dto/app/pay/PayControllerDTO.java @@ -0,0 +1,14 @@ +package com.ruoyi.xq.dto.app.pay; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class PayControllerDTO { + @Schema(description = "订单号") + private String orderNo; + @Schema(description = "微信OpenId jsapi支付必传") + private String openId; +// @Schema(description = "微信支付的时候需要传") +// private String wxAppId; +} diff --git a/ruoyi-xq/src/main/java/com/ruoyi/xq/dto/app/pay/PayOrderInfoDTO.java b/ruoyi-xq/src/main/java/com/ruoyi/xq/dto/app/pay/PayOrderInfoDTO.java new file mode 100644 index 0000000..e175cff --- /dev/null +++ b/ruoyi-xq/src/main/java/com/ruoyi/xq/dto/app/pay/PayOrderInfoDTO.java @@ -0,0 +1,19 @@ + +package com.ruoyi.xq.dto.app.pay; + +import cn.hutool.core.util.NumberUtil; +import lombok.Data; + +import java.math.BigDecimal; + +@Data +public class PayOrderInfoDTO { + private String body; + private String subject; + private BigDecimal price; + private String orderNo; + + public String getPriceFenStr(){ + return NumberUtil.mul(price,100).longValue()+""; + } +} diff --git a/ruoyi-xq/src/main/java/com/ruoyi/xq/dto/app/pay/PayReturnResp.java b/ruoyi-xq/src/main/java/com/ruoyi/xq/dto/app/pay/PayReturnResp.java new file mode 100644 index 0000000..0045293 --- /dev/null +++ b/ruoyi-xq/src/main/java/com/ruoyi/xq/dto/app/pay/PayReturnResp.java @@ -0,0 +1,8 @@ +package com.ruoyi.xq.dto.app.pay; + +import lombok.Data; + +@Data +public class PayReturnResp { + private String data; +} diff --git a/ruoyi-xq/src/main/java/com/ruoyi/xq/manager/PayManager.java b/ruoyi-xq/src/main/java/com/ruoyi/xq/manager/PayManager.java index 0a9b7c8..c9ca533 100644 --- a/ruoyi-xq/src/main/java/com/ruoyi/xq/manager/PayManager.java +++ b/ruoyi-xq/src/main/java/com/ruoyi/xq/manager/PayManager.java @@ -1,8 +1,13 @@ package com.ruoyi.xq.manager; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.xq.domain.VipOrder; import com.ruoyi.xq.domain.VipPrice; +import com.ruoyi.xq.domain.WxTransOrder; import com.ruoyi.xq.dto.app.pay.ConsumeResp; +import com.ruoyi.xq.dto.app.pay.PayOrderInfoDTO; import com.ruoyi.xq.enums.common.OrderTypeEnum; +import com.ruoyi.xq.enums.pay.PayStatusEnum; import com.ruoyi.xq.enums.pay.PlatformTypeEnum; import com.ruoyi.xq.mq.AmqpProducer; import com.ruoyi.xq.mq.handle.dto.CalculateSalesHandleDTO; @@ -30,6 +35,48 @@ public class PayManager { @Autowired private WxTransOrderService wxTransOrderService; + public PayOrderInfoDTO getOrderInfo(String orderNo){ + OrderTypeEnum orderTypeEnum = OrderNoUtil.getType(orderNo); + if(orderTypeEnum == null){ + log.error("订单类型有误!orderNo={}",orderNo); + throw new ServiceException("支付失败,请检查订单号"); + } + PayOrderInfoDTO dto = null; + switch (orderTypeEnum) { + case VIP: + VipOrder vipOrder = vipOrderService.getByOrderNo(orderNo); + if(vipOrder == null){ + throw new ServiceException("订单不存在,请重新下单支付"); + } + if(!PayStatusEnum.READY_PAY.getCode().equals(vipOrder.getPayStatus())){ + throw new ServiceException("订单状态有误,请重新下单支付"); + } + dto = new PayOrderInfoDTO(); + dto.setBody(vipOrder.getBody()); + dto.setSubject(vipOrder.getBody()); + dto.setPrice(vipOrder.getVipPrice()); + dto.setOrderNo(vipOrder.getOrderNo()); + break; + case WX_TRANS: + WxTransOrder wxTransOrder = wxTransOrderService.getByOrderNo(orderNo); + if(wxTransOrder == null){ + throw new ServiceException("订单不存在,请重新下单支付"); + } + if(!PayStatusEnum.READY_PAY.getCode().equals(wxTransOrder.getPayStatus())){ + throw new ServiceException("订单状态有误,请重新下单支付"); + } + dto = new PayOrderInfoDTO(); + dto.setBody(wxTransOrder.getBody()); + dto.setSubject(wxTransOrder.getBody()); + dto.setPrice(wxTransOrder.getWxPrice()); + dto.setOrderNo(wxTransOrder.getOrderNo()); + break; + default: + break; + } + return dto; + } + public void callBack(String orderNo, Map params, String appId, PlatformTypeEnum payTypeEnum){ OrderTypeEnum orderTypeEnum = OrderNoUtil.getType(orderNo);