<!-- 微信支付 -->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>4.4.2.B</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>4.4.0</version>
</dependency>
wx:
pay:
appId: #微信公众号或者小程序等的appid
secret:
mchId: #微信支付商户号
mchKey: #微信支付商户密钥
notifyUrl: http://8.130.135.74:8053/api/order/student/pay/wxCallback #支付回调地址
具体如何获得微信支付所需参数appid等,可以去找其他文章,或者直接找公司要,沙箱环境也需要商户账号才能使用沙箱
package com.ruoyi.common.wx;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author ruoyi
*/
@Configuration
@ConditionalOnClass(WxPayService.class)
@EnableConfigurationProperties(WxPayProperties.class)
@AllArgsConstructor
public class WxPayConfiguration {
private WxPayProperties properties;
@Bean
@ConditionalOnMissingBean
public WxPayService wxService() {
WxPayConfig payConfig = new WxPayConfig();
payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId()));
payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId()));
payConfig.setMchKey(StringUtils.trimToNull(this.properties.getMchKey()));
WxPayService wxPayService = new WxPayServiceImpl();
wxPayService.setConfig(payConfig);
return wxPayService;
}
}
package com.ruoyi.common.wx;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* wxpay pay properties.
*
* @author ruoyi
*/
@Data
@ConfigurationProperties(prefix = "wx.pay")
public class WxPayProperties {
/**
* 设置微信公众号或者小程序等的appid
*/
private String appId;
private String secret;
/**
* 微信支付商户号
*/
private String mchId;
/**
* 微信支付商户密钥
*/
private String mchKey;
/**
* 回调地址
*/
private String notifyUrl;
}
package com.ruoyi.common.wx;
import com.alibaba.fastjson.JSONObject;
import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult;
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderResult;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.util.SignUtils;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
/**
* @description: 支付相关通用服务
*/
@Service
public class PayService {
@Autowired
private WxPayService wxPayService;
/**
* 根据传入的tradeType来进行不同类型的支付
* @param dto 商品参数
* @param tradeType WxPayConstants.TradeType.MWEB为H5支付/WxPayConstants.TradeType.NATIVE为扫码支付/WxPayConstants.TradeType.JSAPI为JSAPI支付
* @return
*/
public Object transactions(WxPayDTO dto,String tradeType) {
try {
if (tradeType != null && !tradeType.isEmpty()){
if (WxPayConstants.TradeType.MWEB.equals(tradeType)){
//H5支付
return noMiniappPay(dto, tradeType);
}else if (WxPayConstants.TradeType.NATIVE.equals(tradeType)){
//NATIVE支付
return noMiniappPay(dto, tradeType);
}else if (WxPayConstants.TradeType.JSAPI.equals(tradeType)){
//JSAPI支付
return miniappPay(dto);
}
}
} catch (Exception e) {
// 处理异常
e.printStackTrace();
return null;
}
return null;
}
private String noMiniappPay(WxPayDTO dto, String tradeType) throws WxPayException {
//设置请求参数
WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
request.setOutTradeNo(dto.getOutTradeNo());
request.setTotalFee(dto.getTotalFee());
request.setBody(dto.getBody());
HttpServletRequest httpServletRequest = getHttpServletRequest();
request.setSpbillCreateIp(httpServletRequest.getRemoteAddr());
request.setNotifyUrl(dto.getNotifyUrl());
request.setProductId(dto.getOutTradeNo());
//设置下单方式
request.setTradeType(tradeType);
// 调用统一下单接口
WxPayNativeOrderResult result = wxPayService.createOrder(request);
// Native支付返回的二维码/H5支付返回的跳转链接
String codeUrl = result.getCodeUrl();
// 返回codeUrl给前端
return codeUrl;
}
/**
*
* @param dto
* @return
*/
@SneakyThrows
public Map<String, String> miniappPay(WxPayDTO dto){
//设置请求参数
WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
.body(dto.getBody())
.totalFee(dto.getTotalFee())
.spbillCreateIp(this.getIpAddress(this.getHttpServletRequest()))
.notifyUrl(dto.getNotifyUrl())
.tradeType(WxPayConstants.TradeType.JSAPI)
.openid(dto.getOpenId())
.outTradeNo(dto.getOutTradeNo())
.timeStart(dto.getTimeStart())
.timeExpire(dto.getTimeExpire())
.build();
request.setSignType(WxPayConstants.SignType.MD5);
//发起微信 统一下单
WxPayUnifiedOrderResult result = null;
result = this.wxPayService.unifiedOrder(request);
//前端启动支付配置
if (StringUtils.isBlank(result.getPrepayId())) {
return null;
}
//返回前端调取jsapi支付的所需参数
return getWxJsapiPayParam(result);
}
/**
* 微信退款
* @param body
* @return
*/
public void wxRefund(WxRefundDTO body) {
WxPayRefundRequest refundRequest = new WxPayRefundRequest();
refundRequest.setOutTradeNo(body.getOutTradeNo());
refundRequest.setOutRefundNo(body.getOutRefundNo());
refundRequest.setTotalFee(body.getTotalFee());
refundRequest.setRefundFee(body.getRefundFee());
refundRequest.setRefundDesc("商品退款");
refundRequest.setNotifyUrl(body.getNotifyUrl());
try {
WxPayRefundResult result = wxPayService.refund(refundRequest);
System.out.println("微信退款成功,返回参数{}" + JSONObject.toJSONString(result));
} catch (WxPayException e) {
System.out.println("微信退款异常,返回参数{}" + JSONObject.toJSONString(e));
}
}
/**
* 获取请求对象
*
* @return
*/
private HttpServletRequest getHttpServletRequest() {
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String key = headerNames.nextElement();
System.out.println("请求头:" + key + "值:" + request.getHeader(key));
}
return request;
}
/**
* 获取到JS支付相关配置
*
* @param weiXinPayOrder
* @return
*/
private Map<String, String> getWxJsapiPayParam(WxPayUnifiedOrderResult weiXinPayOrder) {
WxPayWebDTO wxPayParam = new WxPayWebDTO();
String package_ = "prepay_id=" + weiXinPayOrder.getPrepayId();
wxPayParam.setAppId(weiXinPayOrder.getAppid());
wxPayParam.setTimeStamp(System.currentTimeMillis() / 1000 + "");
wxPayParam.setNonceStr(UUID.randomUUID().toString().replace("-", ""));
wxPayParam.setPackage1(package_);
wxPayParam.setSignType(WxPayConstants.SignType.MD5);
//WxPayParam中属性package1,签名使用key是package
Map<String, String> signParam = new LinkedHashMap<String, String>();
signParam.put("appId", weiXinPayOrder.getAppid());
signParam.put("nonceStr", wxPayParam.getNonceStr());
signParam.put("package", package_);
signParam.put("signType", WxPayConstants.SignType.MD5);
signParam.put("timeStamp", wxPayParam.getTimeStamp());
String paySign = SignUtils.createSign(signParam,WxPayConstants.SignType.MD5, this.wxPayService.getConfig().getMchKey(), new String[0]);
signParam.put("paySign", paySign);
return signParam;
}
}
package com.ruoyi.web.controller.system;
import com.alibaba.fastjson.JSONObject;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.wxpay.sdk.WXPayUtil;
import com.ruoyi.common.utils.DateUtil;
import com.ruoyi.common.wx.PayService;
import com.ruoyi.common.wx.StreamUtils;
import com.ruoyi.common.wx.WxPayDTO;
import com.ruoyi.common.wx.WxPayProperties;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Api(tags = "微信支付相关接口")
@RestController
@Slf4j
@Validated
@RequiredArgsConstructor
@RequestMapping("/pay")
public class WechatPayController {
@Resource
private PayService payService;
@Resource
private WxPayProperties wxPayProperties;
@ApiOperation(value = "微信native下单,返回支付二维码")
@GetMapping("/nativePay")
public Object nativePay(@RequestParam("orderNumber") String orderNumber) {
//todo 业务操作-根据订单编号查询订单信息
//将订单信息中的数据存到WxPayDTO
WxPayDTO payDTO = new WxPayDTO();
payDTO.setBody("商品描述");
//订单总金额,单位为分
payDTO.setTotalFee(1);
//支付回调地址
payDTO.setNotifyUrl(wxPayProperties.getNotifyUrl());
//商品订单编号
payDTO.setOutTradeNo(orderNumber);
//获取时间
Date date = new Date();
String timeStart = DateUtil.formatDateToString(date, "yyyyMMddHHmmss");
//结束时间设置在30分钟后
String timeExpire = DateUtil.formatDateToString(DateUtil.addMinutesToDate(date, 30),"yyyyMMddHHmmss");
//交易起始时间
payDTO.setTimeStart(timeStart);
//交易结束时间
payDTO.setTimeExpire(timeExpire);
Object url = payService.transactions(payDTO, WxPayConstants.TradeType.NATIVE);
return url;
}
@ApiOperation(value = "微信JSAPI下单,返回JS支付相关配置")
@GetMapping("/jsapiPay")
public Object jsapiPay(@RequestParam("orderNumber") String orderNumber) {
//todo 业务操作-根据订单编号查询订单信息
//将订单信息中的数据存到WxPayDTO
WxPayDTO payDTO = new WxPayDTO();
payDTO.setBody("商品描述");
//订单总金额,单位为分
payDTO.setTotalFee(1);
//支付回调地址
payDTO.setNotifyUrl(wxPayProperties.getNotifyUrl());
payDTO.setOutTradeNo("商户订单号");
//获取时间
Date date = new Date();
String timeStart = DateUtil.formatDateToString(date, "yyyyMMddHHmmss");
//结束时间设置在30分钟后
String timeExpire = DateUtil.formatDateToString(DateUtil.addMinutesToDate(date, 30),"yyyyMMddHHmmss");
//交易起始时间
payDTO.setTimeStart(timeStart);
//交易结束时间
payDTO.setTimeExpire(timeExpire);
//todo jsapi下单需要用户的openid
payDTO.setOpenId("openid");
Object url = payService.transactions(payDTO, WxPayConstants.TradeType.JSAPI);
return url;
}
@ApiOperation(value = "微信H5下单,返回跳转链接")
@GetMapping("/h5Pay")
public Object h5Pay(@RequestParam("orderNumber") String orderNumber, HttpServletRequest request) {
//todo 业务操作-根据订单编号查询订单信息
//将订单信息中的数据存到WxPayDTO
WxPayDTO payDTO = new WxPayDTO();
payDTO.setBody("商品描述");
//订单总金额,单位为分
payDTO.setTotalFee(1);
//支付回调地址
payDTO.setNotifyUrl(wxPayProperties.getNotifyUrl());
payDTO.setOutTradeNo("商户订单号");
//获取时间
Date date = new Date();
String timeStart = DateUtil.formatDateToString(date, "yyyyMMddHHmmss");
//结束时间设置在30分钟后
String timeExpire = DateUtil.formatDateToString(DateUtil.addMinutesToDate(date, 30),"yyyyMMddHHmmss");
//交易起始时间
payDTO.setTimeStart(timeStart);
//交易结束时间
payDTO.setTimeExpire(timeExpire);
//todo H5下单需要用户的用户的客户端IP
String remoteAddr = request.getRemoteAddr();
payDTO.setPayerClientIp(remoteAddr);
Object url = payService.transactions(payDTO, WxPayConstants.TradeType.JSAPI);
return url;
}
@ApiOperation(value = "微信支付回调")
@PostMapping("/wxCallback")
public Object wxCallback(HttpServletRequest request, HttpServletResponse response) {
ServletInputStream inputStream = null;
try {
inputStream = request.getInputStream();
String notifyXml = StreamUtils.inputStream2String(inputStream, "utf-8");
log.info(notifyXml);
// 解析返回结果
Map<String, String> notifyMap = WXPayUtil.xmlToMap(notifyXml);
String jsonString = JSONObject.toJSONString(notifyMap);
log.info(jsonString);
// 判断支付是否成功
if ("SUCCESS".equals(notifyMap.get("result_code"))) {
//todo 修改订单状态
// 支付成功:给微信发送我已接收通知的响应 创建响应对象
Map<String, String> returnMap = new HashMap<>();
returnMap.put("return_code", "SUCCESS");
returnMap.put("return_msg", "OK");
String returnXml = WXPayUtil.mapToXml(returnMap);
response.setContentType("text/xml");
log.info("支付成功");
return returnXml;
}else {
//保存回调信息,方便排除问题
}
// 创建响应对象:微信接收到校验失败的结果后,会反复的调用当前回调函数
Map<String, String> returnMap = new HashMap<>();
returnMap.put("return_code", "FAIL");
returnMap.put("return_msg", "");
String returnXml = WXPayUtil.mapToXml(returnMap);
response.setContentType("text/xml");
log.info("校验失败");
return returnXml;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
package com.ruoyi.common.wx;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class WxPayDTO {
/**
* openId
*/
private String openId;
/**
* 字段名:商品描述.
* 变量名:body
* 是否必填:是
* 类型:String(128)
* 示例值: 开发喵会员充值
* 描述:商品简单描述,该字段须严格按照规范传递,具体请见参数规定
*/
private String body;
/**
* 字段名:总金额.
* 变量名:total_fee
* 是否必填:是
* 类型:Int
* 示例值: 543
* 描述:订单总金额,单位为分,详见支付金额
*/
private Integer totalFee;
/**
* 字段名:通知地址.
* 变量名:notify_url
* 是否必填:是
* 类型:String(256)
* 示例值:http://www.weixin.qq.com/wxpay/pay.php
* 描述:接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。
*/
private String notifyUrl;
/**
* 字段名:商户订单号.
* 变量名:out_trade_no
* 是否必填:是
* 类型:String(32)
* 示例值:20230806125346
* 描述:商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号
*/
private String outTradeNo;
/**
* 字段名:交易起始时间.
* 变量名:time_start
* 是否必填:否
* 类型:String(14)
* 示例值:20231225091010
* 描述:订单生成时间,格式为yyyyMMddHHmmss,如2023年12月25日9点10分10秒表示为20231225091010。其他详见时间规则
*/
private String timeStart;
/**
* 字段名:交易结束时间.
* 变量名:time_expire
* 是否必填:否
* 类型:String(14)
* 示例值:20231227091010
* 描述:订单失效时间,格式为yyyyMMddHHmmss,如2023年12月27日9点10分10秒表示为20231227091010。其他详见时间规则
* 注意:最短失效时间间隔必须大于5分钟
*/
private String timeExpire;
/**
* 字段名:用户的客户端IP
* 变量名:payer_client_ip
* 是否必填:是
* 类型:String(14)
* 示例值:14.23.150.211
* 描述:用户的客户端IP,支持IPv4和IPv6两种格式的IP地址。
* 注意:最短失效时间间隔必须大于5分钟
*/
private String payerClientIp;
}
1
powered by kaifamiao