实战开发支付SDK —— 对接微信支付看这一篇文章就够啦(含源码)

Table of Contents

一、引言

二、了解微信支付

2.1、支付方式

2.1.1、付款码支付

2.1.2、JSAPI支付

2.1.3、Native支付

2.1.4、APP支付

2.1.5、H5支付

2.1.6、小程序支付

2.1.7、人脸支付

2.2、名词解释

2.2.1、appid

2.2.2、openid

2.2.3、商户号以及密钥

三、同步 vs 异步

3.1、什么是同步?

3.2、什么是异步?

四、对接Native支付

4.1、文档介绍

4.2、统一下单API介绍

4.3、参数签名介绍

五、统一下单API代码实现

5.1、微信配置类:WxPayConfig

5.2、统一下单接口请求入参:WxPayUnifiedorderRequest

5.3、统一下单接口返回入参:WxPayUnifiedorderResponse

5.4、支付平台枚举:BestPayPlatformEnum

5.5、支付类型枚举:BestPayTypeEnum

5.6、微信请求API定义:WxPayApi

5.7、对外支付请求入参:PayRequest

5.8、对外支付返回入参:PayResponse

5.9、常量定义:WxPayConstants

5.10、对外提供API接口定义:BestPayService

5.11、对外提供API接口实现:BestPayServiceImpl

5.12、微信接入实现类:WxPayServiceImpl

五、单元测试

六、转换二维码

七、总结总结


一、引言

少年,看你骨骼惊奇是个程序员的料,这里有一篇对接微信支付详细教程,维护世界和平就靠你了。

项目搭建:实战开发支付SDK —— 内容介绍以及项目环境搭建

项目工具类:实战开发支付SDK —— 项目结构设计讲解(微信、支付宝)

本文中的所使用的工具类,可在上文友情链接中可以找到

 

二、了解微信支付

想学好支付,首先我们不能急于代码的编写,而是需要弄清楚支付一系列的理论和名词概念,只有弄清楚了才有利于代码的编写。 

微信支付接入文档地址:https://pay.weixin.qq.com/wiki/doc/api/index.html

2.1、支付方式

实战开发支付SDK —— 对接微信支付看这一篇文章就够啦(含源码)_第1张图片

2.1.1、付款码支付

付款码支付是在日常生活中比较常见的,小编每天早上买早餐,在结账时打开付款码,然后商家用扫描枪扫你。这种场景就是付款码支付。 付款码如下图,这种模式是商家扫你

实战开发支付SDK —— 对接微信支付看这一篇文章就够啦(含源码)_第2张图片

2.1.2、JSAPI支付

这种小伙伴一开始可能就不太理解了,什么是JSAPI支付呢?这个是使用微信内部浏览器访问网站,比如说朋友发给你一个H5地址那种商城等等。在页面调用微信支付控件,最后支付进行支付流程。

这种模式往往可能是通过一个公众号,公众号里面有一些内容可能会让用户产生一些付款的行为。但是JSAPI支付可以不用关注公众号直接进行支付,只需要使用微信浏览器访问即可。

2.1.3、Native支付

这种方式也是最常见之一,就是本次项目主要实现支付的方式。

平时用电脑上网浏览网页,如果遇到需要支付的,很多都是直接用微信APP或者支付宝APP进行扫码支付是吧,这种就是Native支付。

这种是用户主动扫码,这种支付方式小编最常听见的就是“扫码支付”,其实在开发上来说不是严谨的。比如我们程序员来交流微信支付沟通,你说一个扫码支付,这样会使对方产生疑问,不了解具体指得是商家扫用户,还是用户主动扫码。

下次在和同事讨论微信支付的时候,一些专业名词还是要能理解的,不然一脸茫然(Native支付? 这是什么玩意)。

2.1.4、APP支付

这个小伙伴肯定都有使用过的,日常生活会经常会使用APP进行支付,然后唤起微信APP,支付完成后跳回去,小编就不多说啦~~~

2.1.5、H5支付

这个看名字是不是和刚刚上面所说的JSAPI支付很相似呢?  JSAPI是通过微信浏览器访问的地址,从而进行支付的流程。而这个H5支付,指的是微信以外的浏览器从外部浏览器唤起微信支付

就是提供一个浏览地址,用手机其他浏览器进行访问的,从而唤起微信支付。

2.1.6、小程序支付

小程序理所当然就是在微信小程序中发起支付,接入小程序支付和JSAPI很相似。

2.1.7、人脸支付

人脸支付小编也没体验经验,场景总的来说,无需掏出手机,进入刷脸支付的时代。


2.2、名词解释

在了解微信有那么多种支付方式,我们还需要弄清楚微信的开放平台,微信对于支付宝来说,稍微比较复杂一点。

应用 平台名 地址
公众号 微信开放平台 https://mp.weixin.qq.com/
小程序 微信开放平台 https://mp.weixin.qq.com/
移动应用 微信开放平台 https://open.weixin.qq.com/

如果需要对接微信的各种支付操作,以上三个开放平台可就不能弄混淆啦,当我们对接支付的时候,首先我们需要获取一定的信息,具体什么信息呢?

2.2.1、appid

这里的appid可不是指的移动app的id,这个appid就需要从刚刚所说的三个开放平台里面获取了。当然这里千万不要获取错了,等会一哥们开发小程序支付,用的是公众号的appid。(可还别不相信,小编我还真碰见过这样的情况

实战开发支付SDK —— 对接微信支付看这一篇文章就够啦(含源码)_第3张图片

对于在开发不同场景下的微信支付,所使用对应的appid,看以下表格。(人脸支付暂时也不清楚哟

                                                      微信支付
  小程序appid 公众号appid 移动应用appid
付款码支付             ✓  
JSAPI支付             ✓  
Native支付             ✓  
APP支付               ✓
H5支付             ✓  
小程序支付           ✓    
人脸支付      

2.2.2、openid

这个openid接触过微信开发的小伙伴肯定有所了解,openid是微信用户在公众号appid下的唯一用户标识,但要注意appid不同,则获取的openid就不同。在使用微信的JSAPI支付的时候,此参数必传。 当然这个openid在开放平台肯定是没有的,需要调用微信接口从而获得。

那么看到这小编想问下,同一个用户在公众号和小程序中的appid是一样的么? 答案肯定是不一样的。

2.2.3、商户号以及密钥

微信支付商户号:商户申请微信支付后,由微信支付分配的商户收款账号。

密钥:可按以下路径设置:微信商户平台(pay.weixin.qq.com)-->账户中心-->账户设置-->API安全-->密钥设置

这两个是开发支付必备、必备、必备的条件,当然这些公司一般都会提供给你的。

三、同步 vs 异步

介绍了微信这么多一些概念性的玩意,那么在科普技术上的内容。 

3.1、什么是同步?

同步很好理解的,发送请求 —— 接收请求 —— 处理业务逻辑 —— 返回处理结果。

你看这里的处理结果是立马反馈的,不需要在进行额外的时间等待。再举个的例子,问同事借个火,同事立马就把火机给你了。

同步一般对于需要获得反馈结果,并且业务逻辑不是很复杂的情况下,可以考虑使用同步。 但随着并发量越来越大,服务压力也就越来越大,这时候就应该考虑异步。

3.2、什么是异步?

异步就是不会立马给你反馈结果,需要有一个等待的过程。

异步往往只是先发送一个请求,这个请求可能会被存放到队列中进行排队,然后之前的队列消息处理完了,在处理本次请求的队列消息,再把处理结果通知给你。

异步就会需要一个等待的时间,微信支付也采用量异步的方式,当我们提交支付订单之后,用户进行付款,微信最后会异步通知给我们,最后的支付结果也需要以微信异步返回的结果为准。(微信只有收到钱了,才会发起异步通知)

四、对接Native支付

4.1、文档介绍

Native接入文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1 (文档需要仔细看哟

场景文档中也有介绍,基本的流程的流程如下

步骤1:商户根据微信支付的规则,为不同商品生成不同的二维码,展示在各种场景,用于用户扫描购买。

步骤2:用户使用微信APP“扫一扫”扫描二维码后,获取商品支付信息,引导用户完成支付。

步骤3:用户确认支付,输入支付密码。

步骤4:支付完成后会提示用户支付成功,商户后台得到支付成功的通知。

Native支付有两种模式,第一种模式流程相对来说稍微麻烦一点,必须要在公众平台后台设置支付回调URL地址。而第二种模式则不需要依赖设置回调的URL,而是在微信支付的接口中,支付回调地址会通过参数的形式传递过去。模式二稍微有一点缺点就是,所生成的二维码有效期只有两个小时。

模式一:开发前,商户必须在公众平台后台设置支付回调URL。URL实现的功能:接收用户扫码后微信支付系统回调的productid和openid。

模式二与模式一相比,流程更为简单,不依赖设置的回调支付URL。商户后台系统先调用微信支付的统一下单接口,微信后台系统返回链接参数code_url,商户后台系统将code_url值生成二维码图片,用户使用微信客户端扫码后发起支付。注意:code_url有效期为2小时,过期后扫码不能再发起支付。

4.2、统一下单API介绍

这里我们采用模式二的方式,不需要在后台设置支付回调URL。 支付类型为:Native支付,请求的参数格式都是XML形式。

除付款码支付场景以外,商户系统先调用该接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易会话标识后再按Native、JSAPI、APP等不同场景生成交易串调起支付。

这个统一下单接口也是我们本次项目,重要实现之一,我们这次使用Native支付的方式调用该接口,直接返回支付链接,生成二维码即可使用。

请求参数,小编把一些重要的列举出来:

公众账号ID (appid):这里是Native支付,用的公众号开放平台的appid。(一般公司提供)

商户号(mch_id):微信支付分配的商户号,在开放平台后台可以获取到。(一般公司提供)

商品描述(body):订单名称,由商务系统生成。

标价金额(total_fee):订单支付金额,也是最后用户扫码需支付的实际金额。

签名(sign):这个签名是最为复杂的,需要通过一系列操作才能获取到,详细查看文章4.3参数签名介绍。

商户订单号(out_trade_no):这个是我们自己商户系统所生成的订单号。

通知地址(notify_url):支付成功后微信异步回调我们的地址。

交易类型(trade_type):JSAPI -JSAPI支付、NATIVE -Native支付(本次项目选用)、APP -APP支付

返回参数,小编把一些重要的列举出来:

返回状态码(return_code):通信标识是否正确,SUCCESS/FAIL,我们拿到微信返回结果之后,先得判断此标识。

返回信息(return_msg):当return_code为FAIL时,返回错误信息。

只有当return_code = SUCCESS,才会有以下字段

业务结果(result_code):SUCCESS/FAIL。

错误代码描述(err_code_des):当result_code为FAIL时返回错误描述。

只有当return_code、result_code 两个字段同时 =  SUCCESS,才会返回以下字段

二维码链接(code_url):这个就是实际支付的二维码。(小编科普下,二维码实际本质就是一段文本内容,扫码二维码其实就是去访问这段内容

4.3、参数签名介绍

微信参考文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3

在我们调用统一下单API的时候,需要一个参数叫做:签名(sign),那这个参数怎么来呢?

第一步:将所有非空的参数按照参数名ASCII码从小到大排序,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串。

第二步:将拼接后的字符串拼接“key=商户密钥”,最后进行MD5运算,再将得到的字符串所有字符转换为大写,得到最后sign。

具体实现代码如下:以下是Java代码,小伙伴可以直接用。

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * @Auther: IT贱男
 * @Date: 2019/12/19 14:42
 * @Description: 微信支付签名
 */
public class WxPaySignature {

    /**
     * 生成签名
     * 对参数按照key=value的格式,并按照参数名ASCII字典序排序如下,MD5返回
     *
     * @param params
     * @param singkKey
     * @return
     */
    public static String sing(Map params, String singkKey) {

        // 照参数名ASCII字典序排序
        SortedMap sortedMap = new TreeMap<>(params);

        // 对参数按照key=value的格式
        StringBuffer sb = new StringBuffer();
        for (String key : sortedMap.keySet()) {
            String value = sortedMap.get(key);
            // 这里是用来校签的时候,排除微信自身回传的sign
            if (key.equals("sign")) {
                continue;
            }
            if (!StringUtils.isBlank(value)) {
                sb.append(key).append("=").append(value).append("&");
            }
        }

        // 拼接API密钥:
        sb.append("key=").append(singkKey);

        // 进行MD5运算,再将得到的字符串所有字符转换为大写
        return DigestUtils.md5Hex(sb.toString()).toUpperCase();
    }


    /**
     * 校签
     *
     * @param params
     * @param singkKey
     * @return
     */
    public static Boolean verify(Map params, String singkKey) {
        String sing = sing(params, singkKey);
        return sing.equals(params.get("sign"));
    }
}

 


五、统一下单API代码实现

终于可以开始写代码了~~~~~

还记得我们在开发支付之前,需要哪些必要条件吗? 当然是先要获取appid,商户号以及密钥了。

项目依赖在这里:实战开发支付SDK —— 内容介绍以及项目环境搭建

项目工具类在这里:实战开发支付SDK —— 项目结构设计讲解(微信、支付宝)

5.1、微信配置类:WxPayConfig

/**
 * @Auther: IT贱男
 * @Date: 2019/12/19 11:42
 * @Description: 微信支付相关配置
 */
@Data
public class WxPayConfig {

    /**
     * 公众号appId
     */
    private String appId;

    /**
     * 商户号
     */
    private String mchId;

    /**
     * 商户密钥
     */
    private String mchKey;

    /**
     * 支付完成后的异步通知地址. 我们这里选用的是模式二,不需要在后台设置支付回调地址
     */
    private String notifyUrl;

}

5.2、统一下单接口请求入参:WxPayUnifiedorderRequest

import lombok.Data;
import org.simpleframework.xml.Element;
import org.simpleframework.xml.Root;

/**
 * @Auther: IT贱男
 * @Date: 2019/12/19 11:03
 * @Description: 微信统一下单接口请求参数
 */
@Data

@Root(name = "xml", strict = false)
public class WxPayUnifiedorderRequest {

    /**
     * 公众账号ID
     */
    @Element(name = "appid")
    private String appid;

    /**
     * 商户号
     */
    @Element(name = "mch_id")
    private String mchId;

    /**
     * 随机字符串
     */
    @Element(name = "nonce_str")
    private String nonceStr;

    /**
     * 签名
     */
    @Element(name = "sign")
    private String sign;

    /**
     * 附加数据 equired = false 这个参数不是必传
     */
    @Element(name = "attach", required = false)
    private String attach;

    /**
     * 商品描述
     */
    @Element(name = "body", required = false)
    private String body;

    /**
     * 商品描述
     */
    @Element(name = "detail", required = false)
    private String detail;

    /**
     * 支付结果通知地址
     */
    @Element(name = "notify_url")
    private String notifyUrl;

    /**
     * 交易类型 JSAPI -JSAPI支付(小程序、公众号)、NATIVE -Native支付、APP -APP支付
     */
    @Element(name = "trade_type")
    private String tradeType;

    /**
     * 用户标识 trade_type=JSAPI时(即JSAPI支付),此参数必传,
     */
    @Element(name = "openid", required = false)
    private String openid;

    /**
     * 商户订单号
     */
    @Element(name = "out_trade_no")
    private String outTradeNo;

    /**
     * 终端IP
     */
    @Element(name = "spbill_create_ip")
    private String spbillCreateIp;

    /**
     * 标价金额
     */
    @Element(name = "total_fee")
    private Integer totalFee;
}

5.3、统一下单接口返回入参:WxPayUnifiedorderResponse

import lombok.Data;
import org.simpleframework.xml.Element;
import org.simpleframework.xml.Root;

/**
 * @Auther: IT贱男
 * @Date: 2019/12/19 11:03
 * @Description: 微信统一下单接口返回参数
 */
@Data

@Root(name = "xml", strict = false)
public class WxPayUnifiedorderResponse {

    /**
     * 返回状态码  SUCCESS/FAIL
     */
    @Element(name = "return_code")
    private String returnCode;

    /**
     * 返回信息
     */
    @Element(name = "return_msg", required = false)
    private String returnMsg;

    /** 以下字段在return_code为SUCCESS的时候有返回. */

    /**
     * 公众账号ID
     */
    @Element(name = "appid", required = false)
    private String appid;

    /**
     * 商户号
     */
    @Element(name = "mch_id", required = false)
    private String mchId;

    /**
     * 设备号
     */
    @Element(name = "device_info", required = false)
    private String deviceInfo;

    /**
     * 随机字符串
     */
    @Element(name = "nonce_str", required = false)
    private String nonceStr;

    /**
     * 签名
     */
    @Element(name = "sign", required = false)
    private String sign;

    /**
     * 业务结果
     */
    @Element(name = "result_code", required = false)
    private String resultCode;

    /**
     * 错误代码
     */
    @Element(name = "err_code", required = false)
    private String errCode;

    /**
     * 错误代码描述
     */
    @Element(name = "err_code_des", required = false)
    private String errCodeDes;

    /** 以下字段在return_code 和result_code都为SUCCESS的时候有返回. */

    /**
     * 交易类型 	 JSAPI -JSAPI支付、 NATIVE -Native支付、APP -APP支付
     */
    @Element(name = "trade_type", required = false)
    private String tradeType;

    /**
     * 预支付交易会话标识
     */
    @Element(name = "prepay_id", required = false)
    private String prepayId;

    /**
     * 二维码链接
     */
    @Element(name = "code_url", required = false)
    private String codeUrl;

}

5.4、支付平台枚举:BestPayPlatformEnum

/**
 * @Auther: IT贱男
 * @Date: 2019/12/19 11:47
 * @Description: 支付平台
 */
@Getter
public enum BestPayPlatformEnum {

    ALIPAY("alipay", "支付宝"),

    WX("wx", "微信"),
    ;

    private String code;

    private String name;

    BestPayPlatformEnum(String code, String name) {
        this.code = code;
        this.name = name;
    }

}

5.5、支付类型枚举:BestPayTypeEnum

import static com.kane.pay.enums.BestPayPlatformEnum.ALIPAY;
import static com.kane.pay.enums.BestPayPlatformEnum.WX;

/**
 * @Auther: IT贱男
 * @Date: 2019/12/19 11:49
 * @Description: 支付类型
 */
@Getter
public enum BestPayTypeEnum {

    ALIPAY_APP("alipay_app", ALIPAY, "支付宝app"),

    ALIPAY_PC("alipay_pc", ALIPAY, "支付宝pc"),

    ALIPAY_WAP("alipay_wap", ALIPAY, "支付宝wap"),

    WXPAY_MP("JSAPI", WX, "微信公众账号支付"),

    WXPAY_MWEB("MWEB", WX, "微信H5支付"),

    WXPAY_NATIVE("NATIVE", WX, "微信Native支付"),

    WXPAY_MINI("JSAPI", WX, "微信小程序支付"),

    WXPAY_APP("APP", WX, "微信APP支付"),;

    private String code;

    private BestPayPlatformEnum platform;

    private String desc;

    BestPayTypeEnum(String code, BestPayPlatformEnum platform, String desc) {
        this.code = code;
        this.platform = platform;
        this.desc = desc;
    }
}

5.6、微信请求API定义:WxPayApi

import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.POST;

/**
 * @Auther: IT贱男
 * @Date: 2019/12/19 15:12
 * @Description: 微信API
 */
public interface WxPayApi {

    /**
     * 统一下单
     *
     * @param body
     * @return
     */
    @POST("/pay/unifiedorder")
    Call unifiedorder(@Body RequestBody body);

   
}

5.7、对外支付请求入参:PayRequest

import com.kane.pay.enums.BestPayTypeEnum;
import lombok.Data;

/**
 * @Auther: IT贱男
 * @Date: 2019/12/19 11:48
 * @Description: 支付请求参数 (针对商户系统的使用)
 */
@Data
public class PayRequest {

    /**
     * 支付方式.
     */
    private BestPayTypeEnum payTypeEnum;

    /**
     * 订单号.
     */
    private String orderId;

    /**
     * 订单金额.
     */
    private Double orderAmount;

    /**
     * 订单名字.
     */
    private String orderName;

    /**
     * 微信openid, 仅微信公众号/小程序支付时需要
     */
    private String openid;

    /**
     * 客户端访问Ip  外部H5支付时必传,需要真实Ip
     * 20191015测试,微信h5支付已不需要真实的ip
     */
    private String spbillCreateIp;

    /**
     * 附加内容,发起支付时传入
     */
    private String attach;


}

5.8、对外支付返回入参:PayResponse

/**
 * @Auther: IT贱男
 * @Date: 2019/12/19 11:44
 * @Description: 返回支付结果信息(针对商户系统的使用)
 */
@Data
public class PayResponse {

    // 以下字段在用来接收微信统一下单接口

    /**
     * 扫码付模式二用来生成二维码
     */
    private String codeUrl;

    /**
     * 支付平台
     */
    private BestPayPlatformEnum payPlatformEnum;

}

5.9、常量定义:WxPayConstants

/**
 * @Auther: IT贱男
 * @Date: 2019/12/19 15:05
 * @Description: 微信支付常量
 */
public interface WxPayConstants {

    String WXPAY_GATEWAY = "https://api.mch.weixin.qq.com/";

    String SUCCESS = "SUCCESS";

}

5.10、对外提供API接口定义:BestPayService


/**
 * @Auther: IT贱男
 * @Date: 2019/12/19 10:55
 * @Description: 支付API定义
 */
public interface BestPayService {

    /**
     * 下单支付
     *
     * @param payRequest
     * @return
     */
    PayResponse pay(PayRequest payRequest);

}

5.11、对外提供API接口实现:BestPayServiceImpl

import com.kane.pay.config.WxPayConfig;
import com.kane.pay.enums.BestPayPlatformEnum;
import com.kane.pay.model.PayRequest;
import com.kane.pay.model.PayResponse;
import com.kane.pay.service.BestPayService;
import com.kane.pay.service.impl.wechat.WxPayServiceImpl;

/**
 * @Auther: IT贱男
 * @Date: 2019/12/19 10:58
 * @Description: 基本实现,本SDK需要接入 WeChat And Alipay
 */
public class BestPayServiceImpl implements BestPayService {

    /**
     * 微信支付配置
     */
    private WxPayConfig wxPayConfig;

    public void setWxPayConfig(WxPayConfig wxPayConfig) {
        this.wxPayConfig = wxPayConfig;
    }

    @Override
    public PayResponse pay(PayRequest payRequest) {
        if (BestPayPlatformEnum.WX == payRequest.getPayTypeEnum().getPlatform()) {
            WxPayServiceImpl wxPayService = new WxPayServiceImpl(wxPayConfig);
            return wxPayService.pay(payRequest);
        }
        throw new RuntimeException("支付类型错误");
    }

}

5.12、微信接入实现类:WxPayServiceImpl

import com.kane.pay.config.WxPayConfig;
import com.kane.pay.constants.WxPayConstants;
import com.kane.pay.enums.BestPayPlatformEnum;
import com.kane.pay.model.PayRequest;
import com.kane.pay.model.PayResponse;
import com.kane.pay.model.wxpay.WxPayApi;
import com.kane.pay.model.wxpay.request.WxPayUnifiedorderRequest;
import com.kane.pay.model.wxpay.response.WxPayUnifiedorderResponse;
import com.kane.pay.service.impl.BestPayServiceImpl;
import com.kane.pay.utils.MapUtil;
import com.kane.pay.utils.MoneyUtil;
import com.kane.pay.utils.RandomUtil;
import com.kane.pay.utils.XmlUtil;
import lombok.extern.slf4j.Slf4j;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.simplexml.SimpleXmlConverterFactory;

import java.io.IOException;

/**
 * @Auther: IT贱男
 * @Date: 2019/12/19 10:58
 * @Description: 微信支付实现
 */
@Slf4j
public class WxPayServiceImpl extends BestPayServiceImpl {

    private WxPayConfig wxPayConfig;

    /**
     * 初始化对象
     */
    private Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(WxPayConstants.WXPAY_GATEWAY)
            .addConverterFactory(SimpleXmlConverterFactory.create())
            .client(new OkHttpClient.Builder()
                    .addInterceptor((new HttpLoggingInterceptor()
                            .setLevel(HttpLoggingInterceptor.Level.BODY)))
                    .build()
            )
            .build();

    public WxPayServiceImpl(WxPayConfig wxPayConfig) {
        this.wxPayConfig = wxPayConfig;
    }

    @Override
    public PayResponse pay(PayRequest payRequest) {
        // 组装微信统一下单的参数
        WxPayUnifiedorderRequest request = new WxPayUnifiedorderRequest();
        // 设置基本信息
        request.setBody(payRequest.getOrderName());
        request.setOutTradeNo(payRequest.getOrderId());
        request.setTradeType(payRequest.getPayTypeEnum().getCode());
        request.setTotalFee(MoneyUtil.YuanToFen(payRequest.getOrderAmount()));
        request.setOpenid(payRequest.getOpenid());

        //小程序和app支付有独立的appid,公众号、h5、native都是公众号的appid
        // TODO 这里小编先直接用公众号的appid,如果有需要可根据支付类型来判断
        request.setAppid(wxPayConfig.getAppId());
        request.setMchId(wxPayConfig.getMchId());
        request.setNotifyUrl(wxPayConfig.getNotifyUrl());
        request.setNonceStr(RandomUtil.getRandomStr());
        // TODO 直接先写固定
        request.setSpbillCreateIp("8.8.8.8");

        // 最后生成sgin WxPaySignature 在上文有,MapUtil工具类请翻到文章顶部,查看项目结构设计讲解章节
        request.setSign(WxPaySignature.sing(MapUtil.buildMap(request), wxPayConfig.getMchKey()));

        RequestBody body = RequestBody.create(MediaType.parse("application/xml; charset=utf-8"), XmlUtil.toString(request));
        Call call = retrofit.create(WxPayApi.class).unifiedorder(body);

        // 发起微信统一下单接口
        Response execute = null;
        try {
            execute = call.execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
        assert execute != null;
        if (!execute.isSuccessful()) {
            throw new RuntimeException("【微信统一支付】发起支付, 网络异常");
        }

        // 获取结果
        WxPayUnifiedorderResponse wxPayUnifiedorderResponse = execute.body();
        if (!wxPayUnifiedorderResponse.getReturnCode().equals(WxPayConstants.SUCCESS)) {
            throw new RuntimeException("【微信统一支付】发起支付失败,ReturnCode != SUCCESS 错误详情:" + wxPayUnifiedorderResponse.getReturnMsg());
        }

        if (!wxPayUnifiedorderResponse.getResultCode().equals(WxPayConstants.SUCCESS)) {
            throw new RuntimeException("【微信统一支付】发起支付失败,ResultCode != SUCCESS错误详情:" + wxPayUnifiedorderResponse.getErrCodeDes());
        }

        // TODO 暂时简单实现,可根据需求返回对应的字段信息
        PayResponse payResponse = new PayResponse();
        payResponse.setCodeUrl(wxPayUnifiedorderResponse.getCodeUrl());
        payResponse.setPayPlatformEnum(BestPayPlatformEnum.WX);
        return payResponse;
    }

  
}

 

五、单元测试

将我们SDK代码写完之后,我们只需要配置微信支付的基本信息,也就是WxPayConfig之后,设置到BestPaySerivce里面去,再配置订单的基本信息,最后返回支付链接。

/**
 * @Auther: IT贱男
 * @Date: 2019/12/19 16:03
 * @Description: 微信接口测试
 */
@SpringBootTest
@RunWith(SpringRunner.class)
public class WxPayService {
 
     @Test
    public void contextLoads() {
        // 微信支付配置
        WxPayConfig config = new WxPayConfig();
        config.setAppId("wxd898fcb017******");
        config.setMchId("148346****");
        config.setMchKey("098F6BCD4621D373C**********");
        config.setNotifyUrl("http://baidu.com");
 
        BestPayServiceImpl bestPayService = new BestPayServiceImpl();
        bestPayService.setWxPayConfig(config);
 
        // 配置订单信息
        PayRequest request = new PayRequest();
        request.setOrderId("3432423423");
        request.setOrderAmount(Double.valueOf("0.01"));
        request.setOrderName("测试商品测试商品");
        request.setPayTypeEnum(BestPayTypeEnum.WXPAY_NATIVE);
 
        //发起支付,返回支付相关结果数据
        PayResponse pay = bestPayService.pay(request);
        // 获取支付二维码 (最终需要前台转成二维码进行显示)
        System.out.println(pay.getCodeUrl());
 
    }
 
}

六、转换二维码

转换地址:https://cli.im/,把微信返回的付款地址复制进去,点击生成二维码即可。

我们只需要返回该付款地址给对应前端,由前端工程师进行二维码转换,该二维码就是最后用户可扫描支付的二维码了。

实战开发支付SDK —— 对接微信支付看这一篇文章就够啦(含源码)_第4张图片

 

七、总结总结

有学习当然就要学会总结内容。

第一:了解微信支持哪些支付。

第二:微信开放平台以及了解appid、以及什么支付方式对应具体哪个appid、商户号以及密钥。

第三:同步和异步有什么区别。

第四:对接Native支付,有两种模式第一种需要在后台设置回调地址,第二种则不需要但是支付链接有效期为两个小时。

第五:熟悉统一下单API,基本的支付方式商户系统先调用该接口在微信支付服务后台生成预支付交易单,所以本章都是重点实现了它,以及熟悉该API基本的重要字段。

第六:了解微信签名规则,先要按字典排序、拼接key=value&的形式、拼接商户密钥、MD5运算、最后转大写。

第七:最后就是本项目最重要的实现了,小编每一个类都是放上源码,基本的类可以复制,但是接口的实现类、以及生成签名比较关键的类,还是建议小伙伴动手写一写。

 

如果对于文本有疑问的地方,小伙伴可以留言评论,本文内容有点多,如有描述不清楚的地方多多指教 ~~~

下一章小编接着该项目讲解如何处理微信支付异步回调通知、查询订单接口的实现。

点赞、关注、不迷路 !!! 

你可能感兴趣的:(实战开发支付SDK)