微信开发系列 -- 微信支付与退款实现

这阵子由于项目需要,需要对公众号的点餐程序添加微信支付与退款功能!想起大二上学期第一次做微信支付时的绝望,现在还记忆犹新。

这一次做微信支付相关的业务,是基于框架来做。如果有做过微信支付的同学,可能你会遇到各种各样懵逼的问题,最有趣的是返回的数据不告诉你大小还是小写,坑是多的吐血。

开发之前,我们需要有企业资质才可以做。如果没有的同学自己想办法!

pom.xml 文件
需要在 pom.xml 加入以下依赖!

	<dependency>
		<groupId>com.github.binarywang</groupId>
		<artifactId>weixin-java-pay</artifactId>
		<version>3.0.0</version>
	</dependency>

application.yml 文件
将 application.yml 中修改自己商户平台的信息,以下 keyPath 是证书,可以在微信支付后台下载,指定相关目录即可。

wx:
  pay:
    appId: #微信公众号或者小程序等的appid
    mchId: #微信支付商户号
    mchKey: #微信支付商户密钥
    keyPath: # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
    tradeType: JSAPI  #JSAPI--公众号支付    NATIVE--原生扫码支付   APP--app支付  

假设我们将证书放到 resources 目录下,那写成以下形式即可。

keyPath: classpath:证书名

还需要准备两个配置文件

WechatPayConfig 文件

import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

/**
 * @author Gentle
 * @date 2019/05/05
 * 微信支付信息注入bean 中
 */
@Component
public class WeChatPayConfig {
    @Autowired
    private WeChatPayProperties properties;

    @Bean
    @ConditionalOnMissingBean
    public WxPayConfig payConfig() {
        WxPayConfig payConfig = new WxPayConfig();
        payConfig.setAppId(this.properties.getAppId());
        payConfig.setMchId(this.properties.getMchId());
        payConfig.setMchKey(this.properties.getMchKey());
        payConfig.setKeyPath(this.properties.getKeyPath());
        payConfig.setTradeType(this.properties.getTradeType());
        payConfig.setNotifyUrl(this.properties.getNotifyUrl());
        return payConfig;
    }
    @Bean
    public WxPayService wxPayService(WxPayConfig payConfig) {
        WxPayService wxPayService = new WxPayServiceImpl();
        wxPayService.setConfig(payConfig);
        return wxPayService;
    }
}

微信支付接口的 Controller

import com.gentle.config.WeChatPayProperties;
import com.gentle.entity.ReturnPayInfoVO;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderResult;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.util.SignUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.math.BigDecimal;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;

/**
 * @author : Gentle
 * @date : 2019/5/17 12:11
 * @description:
 */
@RestController
@RequestMapping(value = "/api/client/pay/")
public class WeChatPayController {

    @Autowired
    private WxPayService wxPayService;
    @Autowired
    WeChatPayProperties weChatPayProperties;

    /**
     * 此处处理订单信息,构建订单数据。
     *
     * 将构建好的支付参数返回到前端,前端调起微信支付
     * @return
     */
    @GetMapping(value = "weChatPay")
    public ReturnPayInfoVO weChatPay() {
        /**
         * 处理内部业务,校验订单等
         */
        final WxPayUnifiedOrderRequest wxPayUnifiedOrderRequest = WxPayUnifiedOrderRequest.newBuilder()
                //调起支付的人的 openId
                .openid("openId")
                //订单编号
                .outTradeNo("我们系统内部订单号")
                //订单金额
                .totalFee(yuanToFee(new BigDecimal(100)))
                //商品描述
                .body("订单信息")
                //获取本地IP
                .spbillCreateIp(InetAddress.getLoopbackAddress().getHostAddress())
                //回调的 URL 地址
                .notifyUrl("http://我们的域名/api/client/pay/weChatPayNotify")
                .build();
        WxPayUnifiedOrderResult wxPayUnifiedOrderResult =null;
        try {
            wxPayUnifiedOrderResult = wxPayService.unifiedOrder(wxPayUnifiedOrderRequest);
        } catch (WxPayException e) {
            e.printStackTrace();
            throw new RuntimeException("微信支付调起失败!");
        }
        //组合参数构建支付
        Map<String, String> paySignInfo = new HashMap<>(5);
        String timeStamp = createTimestamp();
        String nonceStr = String.valueOf(System.currentTimeMillis());
        paySignInfo.put("appId", weChatPayProperties.getAppId());
        paySignInfo.put("nonceStr", nonceStr);
        paySignInfo.put("timeStamp", timeStamp);
        paySignInfo.put("signType", "MD5");
        paySignInfo.put("package", "prepay_id=" + wxPayUnifiedOrderResult.getPrepayId());
        String paySign = SignUtils.createSign(paySignInfo, "MD5", weChatPayProperties.getMchKey(), false);

        //组合支付参数
        ReturnPayInfoVO returnPayInfoVO = new ReturnPayInfoVO();
        returnPayInfoVO.setAppId(weChatPayProperties.getAppId());
        returnPayInfoVO.setNonceStr(nonceStr);
        returnPayInfoVO.setPaySign(paySign);
        returnPayInfoVO.setSignType("MD5");
        returnPayInfoVO.setPrepayId(wxPayUnifiedOrderResult.getPrepayId());
        returnPayInfoVO.setTimeStamp(timeStamp);

        return returnPayInfoVO;
    }

    /**
     *
     * @param xmlData 微信返回的流
     * @return
     */
    @RequestMapping(value = "weChatPayNotify",method = {RequestMethod.GET,RequestMethod.POST})
    public String weChatNotify(@RequestBody String xmlData){

        try {
            final WxPayOrderNotifyResult notifyResult = this.wxPayService.parseOrderNotifyResult(xmlData);
            //这里是存储我们发起支付时订单号的信息,所以取出来
            notifyResult.getOutTradeNo();
            /**
             * 系统内部业务,修改订单状态之类的
             */
            //成功后回调微信信息
            return WxPayNotifyResponse.success("回调成功!");
        } catch (WxPayException e) {
            e.printStackTrace();
            return WxPayNotifyResponse.fail("回调有误!");
        }
    }
    /**
     * 1 块钱转为 100 分
     * 元转分
     *
     * @param bigDecimal 钱数目
     * @return 分
     */
    private int yuanToFee(BigDecimal bigDecimal) {
        return bigDecimal.multiply(new BigDecimal(100)).intValue();
    }
    /**
     * 时间
     *
     * @return 时间戳
     */
    private String createTimestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }
}

上述代码便是位置支付的调起信息和支付成功后回调的处理逻辑了,可以对代码进行适当的修改,便可使用。

微信退款实现:
以下便是微信退款的实现,代码也相对简单,修改以下便可使用,当然要各种参数校验等,否则可能会出现被人恶意调起的可能!

import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.math.BigDecimal;

/**
 * @author : Gentle
 * @date : 2019/5/17 12:38
 * @description:
 */
@RestController
@RequestMapping(value = "/api/client/refund/")
@Slf4j
public class WeChatRefundController {

    private static final String REFUND_SUCCESS = "SUCCESS";

    @Autowired
    private WxPayService wxPayService;

    @PostMapping(value = "weChatRefund")
    public String refund() {
        //申请退款
        WxPayRefundRequest refundInfo = WxPayRefundRequest.newBuilder()
                //订单号
                .outTradeNo("自己系统订单号")
                //退款订单号
                .outRefundNo("自己系统订单号")
                //金额
                .totalFee(yuanToFee(new BigDecimal(100)))
                //退款金额
                .refundFee(yuanToFee(new BigDecimal(100)))
                //todo 回调地址
                .notifyUrl("http://我们系统的域名/api/client/refund/refundNotify")
                .build();
        WxPayRefundResult wxPayRefundResult;
        try {
            wxPayRefundResult = wxPayService.refund(refundInfo);
            //判断退款信息是否正确
            if (REFUND_SUCCESS.equals(wxPayRefundResult.getReturnCode())
                    && REFUND_SUCCESS.equals(wxPayRefundResult.getResultCode())) {
                /**
                 * 系统内部业务逻辑
                 */
                return "正在退款中。。";
            }
        } catch (WxPayException e) {
            log.error("微信退款接口错误信息= {}", e);
        }

        return "退款失败";
    }


    /**
     * 仅支持一次性退款,多次退款需要修改逻辑
     * @param xmlData 微信返回的流数据
     * @return
     */
    @RequestMapping(value = "refundNotify",method = {RequestMethod.GET,RequestMethod.POST})
    public String refundNotify(@RequestBody String xmlData) {

        WxPayRefundNotifyResult wxPayRefundNotifyResult;
        try {
            wxPayRefundNotifyResult = wxPayService.parseRefundNotifyResult(xmlData);
        } catch (WxPayException e) {
            log.error("退款失败,失败信息:{}", e);
            return WxPayNotifyResponse.fail("退款失败");
        }
        //判断你返回状态信息是否正确
        if (REFUND_SUCCESS.equals(wxPayRefundNotifyResult.getReturnCode())) {
            WxPayRefundNotifyResult.ReqInfo reqInfo = wxPayRefundNotifyResult.getReqInfo();
            //判断退款状态
            if (REFUND_SUCCESS.equals(reqInfo.getRefundStatus())) {
                //内部订单号
                String outTradeNo = reqInfo.getOutTradeNo();
                /**
                 * 一、可能会重复回调,需要做防重判断
                 * 二、处理我们系统内部业务,做修改订单状态,释放资源等!
                 */
                return WxPayNotifyResponse.success("退款成功!");
            }
        }
        return WxPayNotifyResponse.fail("回调有误!");
    }
    /**
     * 1 块钱转为 100 分
     * 元转分
     *
     * @param bigDecimal 钱数目
     * @return 分
     */
    private int yuanToFee(BigDecimal bigDecimal) {
        return bigDecimal.multiply(new BigDecimal(100)).intValue();
    }
    /**
     * 时间
     *
     * @return 时间戳
     */
    private String createTimestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }
}

案例相关代码已经发布到 GitHub 和码云上,如有兴趣可以下载学习!
GitHub:
https://github.com/LuckyToMeet-Dian-N/WeChat-Demo/tree/master/wechat-pay-demo
码云:
https://gitee.com/reway_wen/WeChat-Demo/tree/master/wechat-pay-demo

总结:
上述便是微信支付和微信退款的实现!代码不难,只是开发资质倒是个问题。如果有资质的话,那这篇文章可能给到您一些帮助。

有兴趣的同学可以关注公众号,一起学习!
微信开发系列 -- 微信支付与退款实现_第1张图片

你可能感兴趣的:(微信开发系列)