开发喵星球

若依集成微信支付(241)

1. 添加微信支付相关依赖

<!-- 微信支付 -->
<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>

2. 在 application.yml 中配置微信支付相关参数

wx:
  pay:
    appId:  #微信公众号或者小程序等的appid
    secret:
    mchId:  #微信支付商户号
    mchKey:  #微信支付商户密钥
    notifyUrl: http://8.130.135.74:8053/api/order/student/pay/wxCallback #支付回调地址

3. 创建微信支付配置类 WxPayConfiguration

具体如何获得微信支付所需参数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;
  }
}

4. 创建微信支付属性类 WxPayProperties

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;
}

创建微信支付业务类 PayService

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;
    }
}

微信支付接口WechatPayController

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;
    }

}

7. 微信支付DTO

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

   
分类:Java/OOP 作者:无限繁荣, 吴蓉 发表于:2024-06-14 17:13:31 阅读量:172
<<   >>


powered by kaifamiao