微信支付/支付宝支付

文章目录
一、微信支付接入与介绍
1、微信支付产品介绍
2、接入指引
二、支付安全基础(证书/**秘钥**/**签名)**
1、安全基础
2、**对称加密和非对称加密**
3、摘要算法
4、数字签名与证书
4.1 数字签名
4.2 数字证书
4.3 **https协议中的数字证书**
三、基础支付API V3
1、支付配置准备
1.1 引入支付参数
1.2 加载商户私钥
1.3 **获取签名验证器和HttpClient**
1.4 **API字典和相关工具**
1.5 设置全局返回类
2、签名和验签解析
2.1 签名
2.2 验签
3、Native支付
3.1 Native支付流程
3.2 Native下单Api
3.3 支付通知API
4、微信支付查单API
5、微信支付退款API
6、微信支付账单下载
7、基础支付APIv2
四、支付宝支付介绍与环境准备
1、接入介绍
1.1 常规接入流程
1.2 使用沙箱
2、支付参数引入
2.1 引入配置文件
2.2 创建配置文件
3、引入服务端SDK
3.1 引入依赖
3.2 创建客户端连接对象
五、支付宝支付功能开发
1、统一收单下单与支付
1.1 支付调用流程
1.2 接口说明
1.3 发起支付请求
2、支付结果通知
2.1 环境配置
2.2 开发异步通知接口
3、统一收单交易关闭
4、统一收单交易查询
4.1 查单接口调用
4.2 定时查单
4.3 处理查询到的订单
5、统一交易退款
6、收单退款冲退完成通知
7、对账
一、微信支付接入与介绍
1、微信支付产品介绍
付款码支付

用户展示微信钱包内的“付款码”给商家,商家扫描后直接完成支付,适用于线下面对面收银的场景。

JSAPI支付

线下场所:商户展示一个支付二维码,用户使用微信扫描二维码后,输入需要支付的金额,完成支 付。
公众号场景:用户在微信内进入商家公众号,打开某个页面,选择某个产品,完成支付。
PC网站场景:在网站中展示二维码,用户使用微信扫描二维码,输入需要支付的金额,完成支付。
特点:用户在客户端输入支付金额

小程序支付

在微信小程序平台内实现支付的功能。

Native支付

Native支付是指商户展示支付二维码,用户再用微信“扫一扫”完成支付的模式。这种方式适用于PC网站。

特点:商家预先指定支付金额

APP支付

商户通过在移动端独立的APP应用程序中集成微信支付模块,完成支付。

刷脸支付

用户在刷脸设备前通过摄像头刷脸、识别身份后进行的一种支付方式。

2、接入指引
1、获取商户号

微信商户平台:https://pay.weixin.qq.com/ 场景:Native支付

步骤:提交资料 => 签署协议 => 获取商户号

2、获取APPID

微信公众平台:https://mp.weixin.qq.com/

步骤:注册服务号 => 服务号认证 => 获取APPID => 绑定商户号

3、获取API秘钥

APIv2版本的接口需要此秘钥

步骤:登录商户平台 => 选择 账户中心 => 安全中心 => API安全=>设置API密钥

4、获取APIv3秘钥

APIv3版本的接口需要此秘钥

步骤:登录商户平台 => 选择 账户中心 => 安全中心 =>API安全 => 设置APIv3密钥

随机密码生成工具:https://suijimimashengcheng.bmcx.com/

5、申请商户API证书

商户API证书是指由商户申请的,包含商户的商户号、公司名称、公钥信息的证书。

APIv3版本的所有接口都需要;APIv2版本的高级接口需要(如:退款、企业红包、企业付款等)

步骤:登录商户平台 =>选择 账户中心 => 安全中心 => API安全 => 申请API证书

商户证书在商户后台申请:https://pay.weixin.qq.com/index.php/core/cert/api_cert#/

6、获取微信平台证书

微信支付平台证书是指由微信支付 负责申请的,包含微信支付平台标识、公钥信息的证书。商户可以使用平台证书中的公钥进行验签。

可以预先下载,也可以通过编程的方式获取。后面的课程中,我们会通过编程的方式来获取

平台证书的获取:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_0.shtml

注意:以上所有API秘钥和证书需妥善保管防止泄露

二、支付安全基础(证书/秘钥/签名)
1、安全基础
**明文:**加密前的消息叫“明文”(plain text)

**密文:**加密后的文本叫“密文”(cipher text)

**密钥:**只有掌握特殊“钥匙”的人,才能对加密的文本进行解密,这里的“钥匙”就叫做“密钥”(key)

“密钥”就是一个字符串,度量单位是“位”(bit),比如,密钥长度是 128,就是 16 字节的二进制串

**加密:**实现机密性最常用的手段是“加密”(encrypt)

按照密钥的使用方式,加密可以分为两大类:对称加密和非对称加密。

**解密:**使用密钥还原明文的过程叫“解密”(decrypt)

**加密算法:**加密解密的操作过程就是“加密算法”

所有的加密算法都是公开的,而算法使用的“密钥”则必须保密

2、对称加密和非对称加密
对称加密

特点:只使用一个密钥,密钥必须保密,常用的有AES算法
优点:运算速度快
缺点:秘钥需要信息交换的双方共享,一旦被窃取,消息会被破解,无法做到安全的密钥交换
非对称加密

特点:使用两个密钥:公钥和私钥,公钥可以任意分发而私钥保密,常用的有RSA
优点:黑客获取公钥无法破解密文,解决了密钥交换的问题
缺点:运算速度非常慢
混合加密

实际场景中把对称加密和非对称加密结合起来使用

身份认证

公钥加密,私钥解密的作用是加密信息
私钥加密,公钥解密的作用是身份认证
3、摘要算法
摘要算法就是我们常说的散列函数、哈希函数(Hash Function),它能够把任意长度的数据“压缩”成固定长度、而且独一无二的“摘要”字符串,就好像是给这段数据生成了一个数字“指纹”。
摘要算法的作用是保证信息的完整性

特性:

不可逆:只有算法,没有秘钥,只能加密,不能解密难题友好性:想要破解,只能暴力枚举
发散性:只要对原文进行一点点改动,摘要就会发生剧烈变化抗碰撞性:原文不同,计算后的摘要也要不同
常见摘要算法:

MD5、SHA1、SHA2(SHA224、SHA256、SHA384)

4、数字签名与证书
4.1 数字签名
数字签名是使用私钥对摘要加密生成签名,需要由公钥将签名解密后进行验证,实现身份认证和不可否认。但是黑客可以伪造公钥与客户进行通信

4.2 数字证书
数字证书解决“公钥的信任”问题,可以防止黑客伪造公钥。个人不能直接分发公钥,公钥的分发必须使用数字证书,数字证书由CA(证书颁发机构)颁发

4.3 https协议中的数字证书


三、基础支付API V3
1、支付配置准备
1.1 引入支付参数
注:这里使用了尚硅谷的资料

新建 wxpay.properties文件并放置在resources文件夹下

# 微信支付相关参数
# 商户号
wxpay.mch-id=1558950191
# 商户API证书序列号
wxpay.mch-serial-no=34345964330B66427E0D3D28826C4993C77E631F

# 商户私钥文件,放在工程目录下
wxpay.private-key-path=apiclient_key.pem
# APIv3密钥
wxpay.api-v3-key=UDuLFDcmy5Eb6o0nTNZdu6ek4DDh4K8B
# APPID
wxpay.appid=wx74862e0dfcf69954
# 微信服务器地址
wxpay.domain=https://api.mch.weixin.qq.com
# 接收结果通知地址
# 注意:每次重新启动ngrok,都需要根据实际情况修改这个配置
wxpay.notify-domain=https://500c-219-143-130-12.ngrok.io

# APIv2密钥
wxpay.partnerKey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
新建 WxPayConfig.java获取配置文件信息

@Configuration
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix="wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
@Slf4j
public class WxPayConfig {

    // 商户号
    private String mchId;

    // 商户API证书序列号
    private String mchSerialNo;

    // 商户私钥文件
    private String privateKeyPath;

    // APIv3密钥
    private String apiV3Key;

    // APPID
    private String appid;

    // 微信服务器地址
    private String domain;

    // 接收结果通知地址
    private String notifyDomain;

    // APIv2密钥
    private String partnerKey;

    /**
     * 获取商户的私钥文件
     * @param filename
     * @return
     */
    private PrivateKey getPrivateKey(String filename){

        try {
            return PemUtil.loadPrivateKey(new FileInputStream(filename));
        } catch (FileNotFoundException e) {
            throw new RuntimeException("私钥文件不存在", e);
        }
    }

    /**
     * 获取签名验证器
     * @return
     */
    @Bean
    public ScheduledUpdateCertificatesVerifier getVerifier(){

        log.info("获取签名验证器");

        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(privateKeyPath);

        //私钥签名对象
        PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);

        //身份认证对象
        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);

        // 使用定时更新的签名验证器,不需要传入证书
        ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
                wechatPay2Credentials,
                apiV3Key.getBytes(StandardCharsets.UTF_8));

        return verifier;
    }


    /**
     * 获取http请求对象
     * @param verifier
     * @return
     */
    @Bean(name = "wxPayClient")
    public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier){

        log.info("获取httpClient");

        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(privateKeyPath);

        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, privateKey)
                .withValidator(new WechatPay2Validator(verifier));
        // ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient

        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        CloseableHttpClient httpClient = builder.build();

        return httpClient;
    }

    /**
     * 获取HttpClient,无需进行应答签名验证,跳过验签的流程
     */
    @Bean(name = "wxPayNoSignClient")
    public CloseableHttpClient getWxPayNoSignClient(){

        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(privateKeyPath);

        //用于构造HttpClient
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                //设置商户信息
                .withMerchant(mchId, mchSerialNo, privateKey)
                //无需进行签名验证、通过withValidator((response) -> true)实现
                .withValidator((response) -> true);

        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        CloseableHttpClient httpClient = builder.build();

        log.info("== getWxPayNoSignClient END ==");

        return httpClient;
    }

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
最后为了让IDEA可以识别配置文件,将配置文件的图标展示成SpringBoot的图标,同时配置文件的内容可以高亮显示,让配置文件和Java代码之间的对应参数可以自动定位,方便开发,这里需要配置Annotation Processormaven依赖,同时进入File -> Project Structure -> Modules -> 选择小叶子- > 选择+号- >选中配置文件,即可配置成功

<!-- 生成自定义配置的元数据信息 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

1
2
3
4
5
6
7
1.2 加载商户私钥
https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient

引入微信SDK详情查看官网,搜索如何加载商户私钥,实现了请求签名的生成和应答签名的验证

<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-apache-httpclient</artifactId>
    <version>0.4.1</version>
</dependency>
1
2
3
4
5
1.3 获取签名验证器和HttpClient
流程如图所示,其余可查看Github官网,1.1中已经具体写明

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Po2JQaN-1649921278437)(https://pay.weixin.qq.com/wiki/doc/apiv3/assets/img/common/ico-guide/chapter1_5_1.png “”)]

1.4 API字典和相关工具
https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_7_3.shtml

微信Native支付的API列表

同时微信支付 APIv3 使用 JSON 作为消息体的数据交换格式,因此需要引入json转换依赖

<!--json处理-->
<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
</dependency>

1
2
3
4
5
6
最后将Native支付接口写成枚举类,方便调用,以及HttpUtil工具类

//举例
@AllArgsConstructor
@Getter
public enum PayType {
    /**
     * 微信
     */
    WXPAY("微信"),
    
    /**
     * 支付宝
     */
    ALIPAY("支付宝");

    /**
     * 类型
     */
    private final String type;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class HttpUtils {

    /**
     * 将通知参数转化为字符串
     * @param request
     * @return
     */
    public static String readData(HttpServletRequest request) {
        BufferedReader br = null;
        try {
            StringBuilder result = new StringBuilder();
            br = request.getReader();
            for (String line; (line = br.readLine()) != null; ) {
                if (result.length() > 0) {
                    result.append("\n");
                }
                result.append(line);
            }
            return result.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
1.5 设置全局返回类
// 全局返回
@Data
@Accessors(chain = true)
public class R {

    private Integer code; //响应码
    private String message; //响应消息
    private Map<String, Object> data = new HashMap<>();

    public static R ok(){
        R r = new R();
        r.setCode(0);
        r.setMessage("成功");
        return r;
    }

    public static R error(){
        R r = new R();
        r.setCode(-1);
        r.setMessage("失败");
        return r;
    }

    public R data(String key, Object value){
        this.data.put(key, value);
        return this;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2、签名和验签解析
2.1 签名
https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtml

2.2 验签
https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml

3、Native支付
3.1 Native支付流程
https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_4.shtml

3.2 Native下单Api
https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml
https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_7_2.shtml

商户端发起支付请求,微信端创建支付订单并生成支付二维码链接,微信端将支付二维码返回给商户 端,商户端显示支付二维码,用户使用微信客户端扫码后发起支付

/**
 * 创建订单,调用Native支付接口
 * @param productId
 * @return code_url 和 订单号
 * @throws Exception
 */
@Transactional(rollbackFor = Exception.class)
@Override
public Map<String, Object> nativePay(Long productId) throws Exception {

    log.info("生成订单");

    //生成订单
    OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId);
    String codeUrl = orderInfo.getCodeUrl();
    if(orderInfo != null && !StringUtils.isEmpty(codeUrl)){
        log.info("订单已存在,二维码已保存");
        //返回二维码
        Map<String, Object> map = new HashMap<>();
        map.put("codeUrl", codeUrl);
        map.put("orderNo", orderInfo.getOrderNo());
        return map;
    }


    log.info("调用统一下单API");

    //调用统一下单API
    HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));

    // 请求body参数
    Gson gson = new Gson();
    Map paramsMap = new HashMap();
    paramsMap.put("appid", wxPayConfig.getAppid());
    paramsMap.put("mchid", wxPayConfig.getMchId());
    paramsMap.put("description", orderInfo.getTitle());
    paramsMap.put("out_trade_no", orderInfo.getOrderNo());
    paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));

    Map amountMap = new HashMap();
    amountMap.put("total", orderInfo.getTotalFee());
    amountMap.put("currency", "CNY");

    paramsMap.put("amount", amountMap);

    //将参数转换成json字符串
    String jsonParams = gson.toJson(paramsMap);
    log.info("请求参数 ===> {}" + jsonParams);

    StringEntity entity = new StringEntity(jsonParams,"utf-8");
    entity.setContentType("application/json");
    httpPost.setEntity(entity);
    httpPost.setHeader("Accept", "application/json");

    //完成签名并执行请求
    CloseableHttpResponse response = wxPayClient.execute(httpPost);

    try {
        String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
        int statusCode = response.getStatusLine().getStatusCode();//响应状态码
        if (statusCode == 200) { //处理成功
            log.info("成功, 返回结果 = " + bodyAsString);
        } else if (statusCode == 204) { //处理成功,无返回Body
            log.info("成功");
        } else {
            log.info("Native下单失败,响应码 = " + statusCode+ ",返回结果 = " + bodyAsString);
            throw new IOException("request failed");
        }

        //响应结果
        Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
        //二维码
        codeUrl = resultMap.get("code_url");

        //保存二维码
        String orderNo = orderInfo.getOrderNo();
        orderInfoService.saveCodeUrl(orderNo, codeUrl);

        //返回二维码
        Map<String, Object> map = new HashMap<>();
        map.put("codeUrl", codeUrl);
        map.put("orderNo", orderInfo.getOrderNo());

        return map;

    } finally {
        response.close();
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
使用微信SDK自带的验签和签名函数,进行订单创建,同时为了减轻服务器压力,需要缓存二维码,同时定时检查二维吗url是否已经过期,并同步到数据库

3.3 支付通知API
https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_5.shtml

微信支付通过支付通知接口将用户支付成功消息通知给商户

内网穿透
这里使用了ngrok作为内网穿透工具,原因是微信支付回调函数需要通知地址,而我们的程序一般都部署在内网;同时针对微信的回调函数,需要考虑失败、超时等情况,否则微信会持续发送回调通知,直到成功返回
验签工具
参考SDK源码中的 WechatPay2Validator 创建通知验签工具类 WechatPay2ValidatorForRequest
解密工具
验签成功的签名串经过对称加密,需要进行api-v3-key秘钥进行解密,证书和回调解密的AesGcm解密参考AesUtil.java
处理订单
获取微信支付回调函数后,需要更新订单状态以及插入支付记录,同时使用可重入锁来处理并发、重复请求,保证订单记录的唯一性与可靠性
4、微信支付查单API
https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_2.shtml

查单接口是为了确定用户已经完成支付但却未回调到系统的情况,需要增加定时任务,定时查找超时订单与处理超时订单等

@Slf4j
@Component
public class WxPayTask {

    @Resource
    private OrderInfoService orderInfoService;

    @Resource
    private WxPayService wxPayService;

    @Resource
    private RefundInfoService refundInfoService;

    /**
     * 秒 分 时 日 月 周
     * 以秒为例
     * *:每秒都执行
     * 1-3:从第1秒开始执行,到第3秒结束执行
     * 0/3:从第0秒开始,每隔3秒执行1次
     * 1,2,3:在指定的第1、2、3秒执行
     * ?:不指定
     * 日和周不能同时制定,指定其中之一,则另一个设置为?
     */
    //@Scheduled(cron = "0/3 * * * * ?")
    public void task1(){
        log.info("task1 被执行......");
    }

    /**
     * 从第0秒开始每隔30秒执行1次,查询创建超过5分钟,并且未支付的订单
     */
    //@Scheduled(cron = "0/30 * * * * ?")
    public void orderConfirm() throws Exception {
        log.info("orderConfirm 被执行......");

        List<OrderInfo> orderInfoList = orderInfoService.getNoPayOrderByDuration(1, PayType.WXPAY.getType());

        for (OrderInfo orderInfo : orderInfoList) {
            String orderNo = orderInfo.getOrderNo();
            log.warn("超时订单 ===> {}", orderNo);

            //核实订单状态:调用微信支付查单接口
            wxPayService.checkOrderStatus(orderNo);
        }
    }


    /**
     * 从第0秒开始每隔30秒执行1次,查询创建超过5分钟,并且未成功的退款单
     */
    //@Scheduled(cron = "0/30 * * * * ?")
    public void refundConfirm() throws Exception {
        log.info("refundConfirm 被执行......");

        //找出申请退款超过5分钟并且未成功的退款单
        List<RefundInfo> refundInfoList = refundInfoService.getNoRefundOrderByDuration(1);

        for (RefundInfo refundInfo : refundInfoList) {
            String refundNo = refundInfo.getRefundNo();
            log.warn("超时未退款的退款单号 ===> {}", refundNo);

            //核实订单状态:调用微信支付查询退款接口
            wxPayService.checkRefundStatus(refundNo);
        }
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
5、微信支付退款API
退款回调和通知回调类似

6、微信支付账单下载
https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_4_8.shtml

前端Vue下载函数

//下载账单
downloadBill(type){
  //获取账单内容
  billApi.downloadBill(this.billDate, type).then(response => {
    console.log(response)
    //response.data.result是后端传输的数据流,后端已经从微信支付下载完毕数据,
    //将其变成base64data,前端进行excel下载
    const element = document.createElement('a')
    element.setAttribute('href', 'data:application/vnd.ms-excel;charset=utf-8,' + encodeURIComponent(response.data.result))
    element.setAttribute('download', this.billDate + '-' + type)
    element.style.display = 'none'
    element.click()
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
7、基础支付APIv2
https://pay.weixin.qq.com/wiki/doc/api/index.html

四、支付宝支付介绍与环境准备
1、接入介绍
支付宝开发者开发平台:https://open.alipay.com/

1.1 常规接入流程
例如网页&移动应用:https://opendocs.alipay.com/open/200

创建应⽤:选择应⽤类型、填写应⽤基本信息、添加应⽤功能、配置应⽤环境(获取⽀付宝公钥、应⽤公钥、应⽤私钥、⽀付宝⽹关地址,配置接⼝内容加密⽅式)、查看 APPID
绑定应⽤:将开发者账号中的APPID和商家账号PID进⾏绑定
配置秘钥:即创建应⽤中的“配置应⽤环境”步骤
上线应⽤:将应⽤提交审核
签约功能:在商家中⼼上传营业执照、已备案⽹站信息等,提交审核进⾏签约
1.2 使用沙箱
沙箱环境配置:https://opendocs.alipay.com/common/02kkv7
沙箱版支付宝下载与登录:https://open.alipay.com/platform/appDaily.htm?tab=tool
2、支付参数引入
2.1 引入配置文件
创建alipay.properties文件,里面存放支付宝相关参数,这里使用了尚硅谷的真实支付宝参数,沙箱版自行替换参数

# 支付宝支付相关参数
# 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
alipay.app-id=2021003124617201

# 商户PID,卖家支付宝账号ID
alipay.seller-id=2088102040215494

# 支付宝网关
alipay.gateway-url=https://openapi.alipay.com/gateway.do

# 商户私钥,您的PKCS8格式RSA2私钥
alipay.merchant-private-key=MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDmMcbk+ezXjrwG2kTIQugXLpMJnl8b112Bq+TV/yQgL2oMC2alYCXDzyHWyjXLHhpeb9MVrKKqqdifcv3+r0U2rclsmoBVIdmlpk9E3Hi0Ulb7qIoYwgrPUMpSQssCPnyCqoN7Xg+y7PqZhgHQpBOF34lGokc1hAVyHHIt+JybTvDaWr00Em9NwGslw3oV8mDXA1rpoPSEpMyxrGW6kFaOlX08zsYoLJUgL9VkQAJ/WUm5gMpQArSzO9MFL3VwnyEBg2KBNlxuDwj+PJ6D3RB6o4SWID8X7y/UCscNLwTmVr2qLf6zSf3GtX+/jnXSVLTtqqL8bnZNFXKNbfoWNH5ZAgMBAAECggEAVEROkg3npLVMoZmPalwLyEi1bOT73h5Fza1WVPxUhi+1O3mE9u8ug/K0aYOWk6eOcZmwBRQwbBdHBH+8+VnCFZUi0k3wwrlkil5KUGQBD8nAq9lzzEJkYKYrmld3J3gmblLrVOMHDjHwPvkueulFeFFvWFsZhD6zG6XMKoYDFlrq1k4yCfimMrPTSLRxhUtkdbPXt0V8vTgrX9D5z9wFiAebjzRhRRhbxWH4xkdy5WaPI7z+zakIjjrWECdXEVVF7BvahBv1dGtKCGjwRwPw15Bc336yZ2Gqfa9Il06PHY3XCB5YCtE31T1s+Wm620hbtJ1Dk3sDnspoYpV8ZnPHLQKBgQD0QOHErtE6OcFMezWLWjbtak1QWUuxdCYtmVCgWqpPpe/JuFSKUYy6AF6Bd5RBJOa4i+RtrZuMAjM0QzZ/6gCZyWzMo0aZB9KhtC3ftpAKWvlmFdvH08I0GKd/PLY/Wcz5YIgByhcV2VZ410NH2u1nVqKL+jIfpzJOzM3zsLFGdwKBgQDxQ87dsVPxdwqcvAkv4WwFY0Dv1932uscyb5ZjkasWtiQ6TPrfzzkT13Zu81vqXL9eaiTgaP8bYeVUQVVScFiO0r2Ylp6TJzp3nl3qEm8N4DmoA/Vwo5TXoR55HgDUMBv7DMzcBa/PU5J1ErZ1pt6k2uBD0U3sMxIRP+VBpsMFrwKBgQCENM4/GGS1gGdpT1NXHziV3zED6aF35qd3jQHAGfMPc4DMDdLsn2FtmB+PMjtz21Zq04WL/Ckyakpu4maQbAdxNj6GsWXYFQzka9NcwMNMZ5uQrwosKil261VWIHWA6slwvdhAJ7PBJseQVuva69wOUC1hWMZirawkTOS5H42E1wKBgQCuDt6CgDlwXhKQ6vOx0G6vIGEr58/h/fRSBcE4ylHlS7itOvZPW1/xWaO+/eFVHl6NzgQWxokthx39ADl/BUBOoelY2WlD/qwmumFEytHF7/uIpHqBLfLm8f1bIfM1IhQ9tYliPtQMvl1OCxcJoD7GLoZXRvxxqJKjUTaje5z9TwKBgQDul1tsUHKpuWyoXoHQ6j0zqzMPgPohvFGN1fV6MBpHHFDDCLajuCrpj/gQPWcwRpOYArZK7u0cyvn/sWijbdL1v6RtjYt7sASiDRbf2FFraLeRJOu/KKkFZ5UZbXWiR2fs02+sTkLBD2MI16HquJzsS3rK+8Hitgdd/U3PyHDSyQ==

# 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥
alipay.alipay-public-key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAguAlv4mG5/uvo5VjyU2ZAJuZR3VbSbhUN11DKYuNbtaGh9lzysF0N7ZbjWLb8E3TLYsvzSYiDwPUJyVU1uPLld9mXiLQ/k8FOAma1rCG7OFMhWtBWglZq2LxLN68qz8aIsUPbuaqiuIfF+zqg9dQ4y9CrhC18U5cCzpYmqoSbxZPMLGRE28qgs7m9FxmGBjttpa+oHHg2Jf1i79DOtbUCHTrK9Mr6Cfd47dAMQf0OdIuvD+fxYD3fF8tVeJWH+GyibMCojYn66lFUhR1TqKoKZtUxFCcnGbodEhoWr2iTlPTMQzm24EiYODod0xn3GWwigZcJma2tLruW51de6U+dwIDAQAB

# 接口内容加密秘钥,对称秘钥
alipay.content-key=pdF//ropAEqHXxI360iwUg==

# 页面跳转同步通知页面路径
alipay.return-url=http://localhost:8080/#/success

# 服务器异步通知页面路径  需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
# 注意:每次重新启动ngrok,都需要根据实际情况修改这个配置
alipay.notify-url=https://a863-180-174-204-169.ngrok.io/api/ali-pay/trade/notify

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2.2 创建配置文件
在config包创建AlipayClientConfig,这里使用Environment 获取参数,和上面微信支付获取参数方法不同

@Configuration
//加载配置文件
@PropertySource("classpath:alipay.properties")
public class AlipayClientConfig {

    @Resource
    private Environment config;
    
    // 可以通过以下方式获取config.getProperty("alipay.app-id")
}

1
2
3
4
5
6
7
8
9
10
11
3、引入服务端SDK
3.1 引入依赖
参考⽂档:开放平台 => ⽂档 => 开发⼯具 => 服务端SDK => Java => 通⽤版 => Maven项⽬依赖https://search.maven.org/artifact/com.alipay.sdk/alipay-sdk-java

<dependency>
  <groupId>com.alipay.sdk</groupId>
  <artifactId>alipay-sdk-java</artifactId>
  <version>4.23.21.ALL</version>
</dependency>
1
2
3
4
5
3.2 创建客户端连接对象
参考⽂档:开放平台 => ⽂档 => 开发⼯具 => 技术接⼊指南 => 数据签名
https://opendocs.alipay.com/common/02kf5q

参考⽂档中 公钥方式 完善 AlipayClientConfig 类,添加 alipayClient() ⽅法 初始化 AlipayClient 对象

@Configuration
//加载配置文件
@PropertySource("classpath:alipay.properties")
public class AlipayClientConfig {

    @Resource
    private Environment config;

    @Bean
    public AlipayClient alipayClient() throws AlipayApiException {

        AlipayConfig alipayConfig = new AlipayConfig();

        //设置网关地址
        alipayConfig.setServerUrl(config.getProperty("alipay.gateway-url"));
        //设置应用Id
        alipayConfig.setAppId(config.getProperty("alipay.app-id"));
        //设置应用私钥
        alipayConfig.setPrivateKey(config.getProperty("alipay.merchant-private-key"));
        //设置请求格式,固定值json
        alipayConfig.setFormat(AlipayConstants.FORMAT_JSON);
        //设置字符集
        alipayConfig.setCharset(AlipayConstants.CHARSET_UTF8);
        //设置支付宝公钥
        alipayConfig.setAlipayPublicKey(config.getProperty("alipay.alipay-public-key"));
        //设置签名类型
        alipayConfig.setSignType(AlipayConstants.SIGN_TYPE_RSA2);
        //构造client
        AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig);

        return alipayClient;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
五、支付宝支付功能开发
1、统一收单下单与支付
1.1 支付调用流程
https://opendocs.alipay.com/open/270/105899

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mWwD20cH-1653225486947)(https://secure2.wostatic.cn/static/uxiuZngjTEt6V1s3cK7vcG/image.png)]

1.2 接口说明
https://opendocs.alipay.com/apis/028r8t?scene=22

公共请求参数:所有接⼝都需要的参数
请求参数:当前接⼝需要的参数
公共响应参数:所有接⼝的响应中都包含的数据
响应参数:当前接⼝的响应中包含的数据
1.3 发起支付请求
在线调试:https://opendocs.alipay.com/open/02no3m

后端核心

@Service
@Slf4j
public class AliPayServiceImpl implements AliPayService {

    @Resource
    private OrderInfoService orderInfoService;

    @Resource
    private AlipayClient alipayClient;

    @Resource
    private Environment config;

    /**支付宝开放平台接受 request 请求对象后
    * 会为开发者生成一个html 形式的 form表单,包含自动提交的脚本
    * 我们将form表单字符串返回给前端程序,之后前端将会调用自动提交脚本,进行表单的提交
    * 此时,表单会自动提交到action属性所指向的支付宝开放平台中,从而为用户展示一个支付页面
    */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String tradeCreate(Long productId) {

        try {
            //生成订单
            log.info("生成订单");
            // 先查存在就直接返回,不存在就插入
            OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId, PayType.ALIPAY.getType());

            //调用支付宝接口
            AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
            //配置需要的公共请求参数
            //支付完成后,支付宝向谷粒学院发起异步通知的地址
            request.setNotifyUrl(config.getProperty("alipay.notify-url"));
            //支付完成后,我们想让页面跳转回谷粒学院的页面,配置returnUrl
            request.setReturnUrl(config.getProperty("alipay.return-url"));

            //组装当前业务方法的请求参数
            JSONObject bizContent = new JSONObject();
            bizContent.put("out_trade_no", orderInfo.getOrderNo());
            BigDecimal total = new BigDecimal(orderInfo.getTotalFee().toString()).divide(new BigDecimal("100"));
            bizContent.put("total_amount", total);
            bizContent.put("subject", orderInfo.getTitle());
            bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");

            request.setBizContent(bizContent.toString());

            //执行请求,调用支付宝接口
            AlipayTradePagePayResponse response = alipayClient.pageExecute(request);

            if(response.isSuccess()){
                log.info("调用成功,返回结果 ===> " + response.getBody());
                return response.getBody();
            } else {
                log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
                throw new RuntimeException("创建支付交易失败");
            }
        } catch (AlipayApiException e) {
            e.printStackTrace();
            throw new RuntimeException("创建支付交易失败");
        }
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
前端核心

//确认支付
    toPay() {
      //禁用按钮,防止重复提交
      this.payBtnDisabled = true

      //微信支付
      if (this.payOrder.payType === 'wxpay') {
       ...
        //支付宝支付
      } else if (this.payOrder.payType === 'alipay') {

        //调用支付宝统一收单下单并支付页面接口
        aliPayApi.tradePagePay(this.payOrder.productId).then((response) => {
          //将支付宝返回的表单字符串写在浏览器中,表单会自动触发submit提交
          document.write(response.data.formStr)
        })
      }
    },
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
前端API方法

import request from '@/utils/request'

export default{

  //发起支付请求
  tradePagePay(productId) {
    return request({
      url: '/api/ali-pay/trade/page/pay/' + productId,
      method: 'post'
    })
  }
 }
1
2
3
4
5
6
7
8
9
10
11
12
2、支付结果通知
2.1 环境配置
在 AliPayServiceImpl 的 tradeCreate ⽅法中设置异步通知地址。同时启动ngrok内网穿透,上面微信支付已经说明,注意要修改成自己的配置

2.2 开发异步通知接口
https://opendocs.alipay.com/open/270/105902#异步通知参数

AliPayController

@ApiOperation("支付通知")
@PostMapping("/trade/notify")
public String tradeNotify(@RequestParam Map<String, String> params){

    log.info("支付通知正在执行");
    log.info("通知参数 ===> {}", params);

    String result = "failure";

    try {
        //异步通知验签
        boolean signVerified = AlipaySignature.rsaCheckV1(
                params,
                config.getProperty("alipay.alipay-public-key"),
                AlipayConstants.CHARSET_UTF8,
                AlipayConstants.SIGN_TYPE_RSA2); //调用SDK验证签名

        if(!signVerified){
            //验签失败则记录异常日志,并在response中返回failure.
            log.error("支付成功异步通知验签失败!");
            return result;
        }

        // 验签成功后
        log.info("支付成功异步通知验签成功!");

        //按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验,
        //1 商户需要验证该通知数据中的 out_trade_no 是否为商户系统中创建的订单号
        String outTradeNo = params.get("out_trade_no");
        OrderInfo order = orderInfoService.getOrderByOrderNo(outTradeNo);
        if(order == null){
            log.error("订单不存在");
            return result;
        }

        //2 判断 total_amount 是否确实为该订单的实际金额(即商户订单创建时的金额)
        String totalAmount = params.get("total_amount");
        int totalAmountInt = new BigDecimal(totalAmount).multiply(new BigDecimal("100")).intValue();
        int totalFeeInt = order.getTotalFee().intValue();
        if(totalAmountInt != totalFeeInt){
            log.error("金额校验失败");
            return result;
        }

        //3 校验通知中的 seller_id(或者 seller_email) 是否为 out_trade_no 这笔单据的对应的操作方
        String sellerId = params.get("seller_id");
        String sellerIdProperty = config.getProperty("alipay.seller-id");
        if(!sellerId.equals(sellerIdProperty)){
            log.error("商家pid校验失败");
            return result;
        }

        //4 验证 app_id 是否为该商户本身
        String appId = params.get("app_id");
        String appIdProperty = config.getProperty("alipay.app-id");
        if(!appId.equals(appIdProperty)){
            log.error("appid校验失败");
            return result;
        }

        //在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS时,
        // 支付宝才会认定为买家付款成功。
        String tradeStatus = params.get("trade_status");
        if(!"TRADE_SUCCESS".equals(tradeStatus)){
            log.error("支付未成功");
            return result;
        }

        //处理业务 修改订单状态 记录支付日志
        aliPayService.processOrder(params);

        //校验成功后在response中返回success并继续商户自身业务处理,校验失败返回failure
        result = "success";
    } catch (AlipayApiException e) {
        e.printStackTrace();
    }
    return result;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
AliPayService

/**
 * 处理订单
 * @param params
 */
@Transactional(rollbackFor = Exception.class)
@Override
public void processOrder(Map<String, String> params) {

    log.info("处理订单");

    //获取订单号
    String orderNo = params.get("out_trade_no");

    /*在对业务数据进行状态检查和处理之前,
    要采用数据锁进行并发控制,
    以避免函数重入造成的数据混乱*/
    //尝试获取锁:
    // 成功获取则立即返回true,获取失败则立即返回false。不必一直等待锁的释放
    if(lock.tryLock()) {
        try {

            //处理重复通知
            //接口调用的幂等性:无论接口被调用多少次,以下业务执行一次
            String orderStatus = orderInfoService.getOrderStatus(orderNo);
            if (!OrderStatus.NOTPAY.getType().equals(orderStatus)) {
                return;
            }

            //更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);

            //记录支付日志
            paymentInfoService.createPaymentInfoForAliPay(params);

        } finally {
            //要主动释放锁
            lock.unlock();
        }
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
3、统一收单交易关闭
https://opendocs.alipay.com/apis/028wob

/**
 * 用户取消订单
 * @param orderNo
 */
@Override
public void cancelOrder(String orderNo) {

    //调用支付宝提供的统一收单交易关闭接口
    this.closeOrder(orderNo);

    //更新用户订单状态
    orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CANCEL);
}
    
    
/**
 * 关单接口的调用
 * @param orderNo 订单号
 */
private void closeOrder(String orderNo) {

    try {
        log.info("关单接口的调用,订单号 ===> {}", orderNo);

        AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();
        JSONObject bizContent = new JSONObject();
        bizContent.put("out_trade_no", orderNo);
        request.setBizContent(bizContent.toString());
        AlipayTradeCloseResponse response = alipayClient.execute(request);

        if(response.isSuccess()){
            log.info("调用成功,返回结果 ===> " + response.getBody());
        } else {
            log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
            //throw new RuntimeException("关单接口的调用失败");
        }

    } catch (AlipayApiException e) {
        e.printStackTrace();
        throw new RuntimeException("关单接口的调用失败");
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
注意:针对⼆维码⽀付,只有经过扫码的订单才在⽀付宝端有交易记录。针对⽀付宝账号⽀付,只有经过登录的订单才在⽀付宝端有交易记录。

4、统一收单交易查询
https://opendocs.alipay.com/apis/028woa

4.1 查单接口调用
商户后台未收到异步⽀付结果通知时,商户应该主动调⽤《统⼀收单线下交易查询接⼝》,同步订单状态。

/**
 * 查询订单
 * @param orderNo
 * @return 返回订单查询结果,如果返回null则表示支付宝端尚未创建订单
 */
@Override
public String queryOrder(String orderNo) {

    try {
        log.info("查单接口调用 ===> {}", orderNo);

        AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
        JSONObject bizContent = new JSONObject();
        bizContent.put("out_trade_no", orderNo);
        request.setBizContent(bizContent.toString());

        AlipayTradeQueryResponse response = alipayClient.execute(request);
        if(response.isSuccess()){
            log.info("调用成功,返回结果 ===> " + response.getBody());
            return response.getBody();
        } else {
            log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
            //throw new RuntimeException("查单接口的调用失败");
            return null;//订单不存在
        }

    } catch (AlipayApiException e) {
        e.printStackTrace();
        throw new RuntimeException("查单接口的调用失败");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
4.2 定时查单
创建定时任务

@Slf4j
@Component
public class AliPayTask {

    @Resource
    private OrderInfoService orderInfoService;

    @Resource
    private AliPayService aliPayService;

    /**
     * 从第0秒开始每隔30秒执行1次,查询创建超过5分钟,并且未支付的订单
     */
    @Scheduled(cron = "0/30 * * * * ?")
    public void orderConfirm(){

        log.info("orderConfirm 被执行......");

        List<OrderInfo> orderInfoList = orderInfoService.getNoPayOrderByDuration(1, PayType.ALIPAY.getType());

        for (OrderInfo orderInfo : orderInfoList) {
            String orderNo = orderInfo.getOrderNo();
            log.warn("超时订单 ===> {}", orderNo);

            //核实订单状态:调用支付宝查单接口
            aliPayService.checkOrderStatus(orderNo);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
4.3 处理查询到的订单
/**
 * 根据订单号调用支付宝查单接口,核实订单状态
 * 如果订单未创建,则更新商户端订单状态
 * 如果订单未支付,则调用关单接口关闭订单,并更新商户端订单状态
 * 如果订单已支付,则更新商户端订单状态,并记录支付日志
 * @param orderNo
 */
@Override
public void checkOrderStatus(String orderNo) {

    log.warn("根据订单号核实订单状态 ===> {}", orderNo);

    String result = this.queryOrder(orderNo);

    //订单未创建
    if(result == null){
        log.warn("核实订单未创建 ===> {}", orderNo);
        //更新本地订单状态
        orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CLOSED);
    }

    //解析查单响应结果
    Gson gson = new Gson();
    HashMap<String, LinkedTreeMap> resultMap = gson.fromJson(result, HashMap.class);
    LinkedTreeMap alipayTradeQueryResponse = resultMap.get("alipay_trade_query_response");

    String tradeStatus = (String)alipayTradeQueryResponse.get("trade_status");
    if(AliPayTradeState.NOTPAY.getType().equals(tradeStatus)){
        log.warn("核实订单未支付 ===> {}", orderNo);

        //如果订单未支付,则调用关单接口关闭订单
        this.closeOrder(orderNo);

        // 并更新商户端订单状态
        orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CLOSED);
    }

    if(AliPayTradeState.SUCCESS.getType().equals(tradeStatus)){
        log.warn("核实订单已支付 ===> {}", orderNo);

        //如果订单已支付,则更新商户端订单状态
        orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);

        //并记录支付日志
        paymentInfoService.createPaymentInfoForAliPay(alipayTradeQueryResponse);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
5、统一交易退款
https://opendocs.alipay.com/apis/028sm9

/**
 * 退款
 * @param orderNo
 * @param reason
 */
@Transactional(rollbackFor = Exception.class)
@Override
public void refund(String orderNo, String reason) {

    try {
        log.info("调用退款API");

        //创建退款单
        RefundInfo refundInfo = refundsInfoService.createRefundByOrderNoForAliPay(orderNo, reason);

        //调用统一收单交易退款接口
        AlipayTradeRefundRequest request = new AlipayTradeRefundRequest ();

        //组装当前业务方法的请求参数
        JSONObject bizContent = new JSONObject();
        bizContent.put("out_trade_no", orderNo);//订单编号
        BigDecimal refund = new BigDecimal(refundInfo.getRefund().toString()).divide(new BigDecimal("100"));
        //BigDecimal refund = new BigDecimal("2").divide(new BigDecimal("100"));
        bizContent.put("refund_amount", refund);//退款金额:不能大于支付金额
        bizContent.put("refund_reason", reason);//退款原因(可选)

        request.setBizContent(bizContent.toString());

        //执行请求,调用支付宝接口
        AlipayTradeRefundResponse response = alipayClient.execute(request);

        if(response.isSuccess()){
            log.info("调用成功,返回结果 ===> " + response.getBody());

            //更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS);

            //更新退款单
            refundsInfoService.updateRefundForAliPay(
                    refundInfo.getRefundNo(),
                    response.getBody(),
                    AliPayTradeState.REFUND_SUCCESS.getType()); //退款成功

        } else {
            log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());

            //更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_ABNORMAL);

            //更新退款单
            refundsInfoService.updateRefundForAliPay(
                    refundInfo.getRefundNo(),
                    response.getBody(),
                    AliPayTradeState.REFUND_ERROR.getType()); //退款失败
        }


    } catch (AlipayApiException e) {
        e.printStackTrace();
        throw new RuntimeException("创建退款申请失败");
    }
}

6、收单退款冲退完成通知
https://opendocs.alipay.com/apis/029yy3

退款存在退到银⾏卡场景下时,收单会根据银⾏回执消息发送退款完成信息。开发流程类似⽀付结果通知。

7、对账
https://opendocs.alipay.com/apis/028woc

查询对账单下载地址接⼝

/**
 * 申请账单
 * @param billDate
 * @param type
 * @return
 */
@Override
public String queryBill(String billDate, String type) {

    try {

        AlipayDataDataserviceBillDownloadurlQueryRequest request = new AlipayDataDataserviceBillDownloadurlQueryRequest();
        JSONObject bizContent = new JSONObject();
        bizContent.put("bill_type", type);
        bizContent.put("bill_date", billDate);
        request.setBizContent(bizContent.toString());
        AlipayDataDataserviceBillDownloadurlQueryResponse response = alipayClient.execute(request);

        if(response.isSuccess()){
            log.info("调用成功,返回结果 ===> " + response.getBody());

            //获取账单下载地址
            Gson gson = new Gson();
            HashMap<String, LinkedTreeMap> resultMap = gson.fromJson(response.getBody(), HashMap.class);
            LinkedTreeMap billDownloadurlResponse = resultMap.get("alipay_data_dataservice_bill_downloadurl_query_response");
            String billDownloadUrl = (String)billDownloadurlResponse.get("bill_download_url");

            return billDownloadUrl;
        } else {
            log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
            throw new RuntimeException("申请账单失败");
        }

    } catch (AlipayApiException e) {
        e.printStackTrace();
        throw new RuntimeException("申请账单失败");
    }
}
————————————————
版权声明:本文为CSDN博主「魅Lemon」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lemon_TT/article/details/124173111

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值