SpringBoot集成支付宝,看这一篇就够了。

news2025/3/1 22:59:04

前 言

在开始集成支付宝支付之前,我们需要准备一个支付宝商家账户,如果是个人开发者,可以通过注册公司或者让有公司资质的单位进行授权,后续在集成相关API的时候需要提供这些信息。
下面我以电脑网页端在线支付为例,介绍整个从集成、测试到上线的具体流程。

1. 预期效果展示

在开始之前我们先看下我们要达到的最后效果,具体如下:

  • 前端点击支付跳转到支付宝界面
  • 支付宝界面展示付款二维码
  • 用户手机端支付
  • 完成支付,支付宝回调开发者指定的url

在这里插入图片描述

2. 开发流程

2.1 沙盒调试

支付宝为我们准备了完善的沙盒开发环境,我们可以先在沙盒环境调试好程序,后续新建好应用并成功上线后,把程序中对应的参数替换为线上参数即可。
1. 创建沙盒应用
直接进入 https://open.alipay.com/develop/sandbox/app 创建沙盒应用即可,
在这里插入图片描述
这里因为是测试环境,我们就选择系统默认密钥就行了,下面选择公钥模式,另外应用网关地址就是用户完成支付之后,支付宝会回调的url。在开发环境中,我们可以采用内网穿透的方式,将我们本机的端口暴露在某个公网地址上,这里推荐 https://natapp.cn/ ,可以免费注册使用。
2. SpringBoot代码实现
在创建好沙盒应用,获取到密钥,APPID,商家账户PID等信息之后,就可以在测试环境开发集成对应的API了。这里我以电脑端支付API为例,介绍如何进行集成。
关于电脑网站支付的详细产品介绍和API接入文档可以参考:https://opendocs.alipay.com/open/repo-0038oa?ref=api 和 https://opendocs.alipay.com/open

  • 步骤1, 添加alipay sdk对应的Maven依赖。
<!-- alipay -->  
<dependency>  
   <groupId>com.alipay.sdk</groupId>  
   <artifactId>alipay-sdk-java</artifactId>  
   <version>4.35.132.ALL</version>  
</dependency>
  • 步骤2,添加支付宝下单、支付成功后同步调用和异步调用的接口。
    这里需要注意,同步接口是用户完成支付后会自动跳转的地址,因此需要是Get请求。异步接口,是用户完成支付之后,支付宝会回调来通知支付结果的地址,所以是POST请求。
@RestController  
@RequestMapping("/alipay")  
public class AliPayController {  

    @Autowired  
    AliPayService aliPayService;  

    @PostMapping("/order")  
    public GenericResponse<Object> placeOrderForPCWeb(@RequestBody AliPayRequest aliPayRequest) {  
        try {  
            return aliPayService.placeOrderForPCWeb(aliPayRequest);  
        } catch (IOException e) {  
            throw new RuntimeException(e);  
        }  
    }  

    @PostMapping("/callback/async")  
    public String asyncCallback(HttpServletRequest request) {  
        return aliPayService.orderCallbackInAsync(request);  
    }  

    @GetMapping("/callback/sync")  
    public void syncCallback(HttpServletRequest request, HttpServletResponse response) {  
        aliPayService.orderCallbackInSync(request, response);  
    }  

}
  • 步骤3,实现Service层代码
    这里针对上面controller中的三个接口,分别完成service层对应的方法。下面是整个支付的核心流程,其中有些地方需要根据你自己的实际情况进行保存订单到DB或者检查订单状态的操作,这个可以根据实际业务需求进行设计。
public class AliPayService {  

    @Autowired  
    AliPayHelper aliPayHelper;  

    @Resource  
    AlipayConfig alipayConfig;  

    @Transactional(rollbackFor = Exception.class)  
    public GenericResponse<Object> placeOrderForPCWeb(AliPayRequest aliPayRequest) throws IOException {  
        log.info("【请求开始-在线购买-交易创建】*********统一下单开始*********");  

        String tradeNo = aliPayHelper.generateTradeNumber();  

        String subject = "购买套餐1";  
        Map<String, Object> map = aliPayHelper.placeOrderAndPayForPCWeb(tradeNo, 100, subject);  

        if (Boolean.parseBoolean(String.valueOf(map.get("isSuccess")))) {  
            log.info("【请求开始-在线购买-交易创建】统一下单成功,开始保存订单数据");  

            //保存订单信息  
            // 添加你自己的业务逻辑,主要是保存订单数据

            log.info("【请求成功-在线购买-交易创建】*********统一下单结束*********");  
            return new GenericResponse<>(ResponseCode.SUCCESS, map.get("body"));  
        }else{  
            log.info("【失败:请求失败-在线购买-交易创建】*********统一下单结束*********");  
            return new GenericResponse<>(ResponseCode.INTERNAL_ERROR, String.valueOf(map.get("subMsg")));  
        }  
    }  

    // sync return page  
    public void orderCallbackInSync(HttpServletRequest request, HttpServletResponse response) {  
        try {  
            OutputStream outputStream = response.getOutputStream();  
            //通过设置响应头控制浏览器以UTF-8的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码  
            response.setHeader("content-type", "text/html;charset=UTF-8");  
            String outputData = "支付成功,请返回网站并刷新页面。";  

            /**  
             * data.getBytes()是一个将字符转换成字节数组的过程,这个过程中一定会去查码表,  
             * 如果是中文的操作系统环境,默认就是查找查GB2312的码表,  
             */  
            byte[] dataByteArr = outputData.getBytes("UTF-8");//将字符转换成字节数组,指定以UTF-8编码进行转换  
            outputStream.write(dataByteArr);//使用OutputStream流向客户端输出字节数组  
        } catch (IOException e) {  
            throw new RuntimeException(e);  
        }  
    }  

    public String orderCallbackInAsync(HttpServletRequest request) {  
        try {  
            Map<String, String> map = aliPayHelper.paramstoMap(request);  
            String tradeNo = map.get("out_trade_no");  
            String sign = map.get("sign");  
            String content = AlipaySignature.getSignCheckContentV1(map);  
            boolean signVerified = aliPayHelper.CheckSignIn(sign, content);  

            // check order status  
            // 这里在DB中检查order的状态,如果已经支付成功,无需再次验证。
            if(DB中拿到order,并且判断order是否支付成功过){  
                log.info("订单:" + tradeNo + " 已经支付成功,无需再次验证。");  
                return "success";  
            }  

            //验证业务数据是否一致  
            if(!checkData(map, order)){  
                log.error("返回业务数据验证失败,订单:" + tradeNo );  
                return "返回业务数据验证失败";  
            }  
            //签名验证成功  
            if(signVerified){  
                log.info("支付宝签名验证成功,订单:" + tradeNo);  
                // 验证支付状态  
                String tradeStatus = request.getParameter("trade_status");  
                if(tradeStatus.equals("TRADE_SUCCESS")){  
                    log.info("支付成功,订单:"+tradeNo);  
                    // 更新订单状态,执行一些业务逻辑

                    return "success";  
                }else{  
                    System.out.println("支付失败,订单:" + tradeNo );  
                    return "支付失败";  
                }  
            }else{  
                log.error("签名验证失败,订单:" + tradeNo );  
                return "签名验证失败.";  
            }  
        } catch (IOException e) {  
            log.error("IO exception happened ", e);  
            throw new RuntimeException(ResponseCode.INTERNAL_ERROR, e.getMessage());  
        }  
    }  


    public boolean checkData(Map<String, String> map, OrderInfo order) {  
        log.info("【请求开始-交易回调-订单确认】*********校验订单确认开始*********");  

        //验证订单号是否准确,并且订单状态为待支付  
        if(验证订单号是否准确,并且订单状态为待支付){  
            float amount1 = Float.parseFloat(map.get("total_amount"));  
            float amount2 = (float) order.getOrderAmount();  
            //判断金额是否相等  
            if(amount1 == amount2){  
                //验证收款商户id是否一致  
                if(map.get("seller_id").equals(alipayConfig.getPid())){  
                    //判断appid是否一致  
                    if(map.get("app_id").equals(alipayConfig.getAppid())){  
                        log.info("【成功:请求开始-交易回调-订单确认】*********校验订单确认成功*********");  
                        return true;                    }  
                }  
            }  
        }  
        log.info("【失败:请求开始-交易回调-订单确认】*********校验订单确认失败*********");  
        return false;    }  
}
  • 步骤4,实现alipayHelper类。这个类里面对支付宝的接口进行封装。
public class AliPayHelper {  

    @Resource  
    private AlipayConfig alipayConfig;  

    //返回数据格式  
    private static final String FORMAT = "json";  
    //编码类型  
    private static final String CHART_TYPE = "utf-8";  
    //签名类型  
    private static final String SIGN_TYPE = "RSA2";  

    /*支付销售产品码,目前支付宝只支持FAST_INSTANT_TRADE_PAY*/  
    public static final String PRODUCT_CODE = "FAST_INSTANT_TRADE_PAY";  

    private static AlipayClient alipayClient = null;  

    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");  
    private static final Random random = new Random();  

    @PostConstruct  
    public void init(){  
        alipayClient = new DefaultAlipayClient(  
                alipayConfig.getGateway(),  
                alipayConfig.getAppid(),  
                alipayConfig.getPrivateKey(),  
                FORMAT,  
                CHART_TYPE,  
                alipayConfig.getPublicKey(),  
                SIGN_TYPE);  
    };  

    /*================PC网页支付====================*/  
    /**  
     * 统一下单并调用支付页面接口  
     * @param outTradeNo  
     * @param totalAmount  
     * @param subject  
     * @return  
     */  
    public Map<String, Object> placeOrderAndPayForPCWeb(String outTradeNo, float totalAmount, String subject){  
        AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();  
        request.setNotifyUrl(alipayConfig.getNotifyUrl());  
        request.setReturnUrl(alipayConfig.getReturnUrl());  
        JSONObject bizContent = new JSONObject();  
        bizContent.put("out_trade_no", outTradeNo);  
        bizContent.put("total_amount", totalAmount);  
        bizContent.put("subject", subject);  
        bizContent.put("product_code", PRODUCT_CODE);  

        request.setBizContent(bizContent.toString());  
        AlipayTradePagePayResponse response = null;  
        try {  
            response = alipayClient.pageExecute(request);  
        } catch (AlipayApiException e) {  
            e.printStackTrace();  
        }  
        Map<String, Object> resultMap = new HashMap<>();  
        resultMap.put("isSuccess", response.isSuccess());  
        if(response.isSuccess()){  
            log.info("调用成功");  
            log.info(JSON.toJSONString(response));  
            resultMap.put("body", response.getBody());  
        } else {  
            log.error("调用失败");  
            log.error(response.getSubMsg());  
            resultMap.put("subMsg", response.getSubMsg());  
        }  
        return resultMap;  
    }  

    /**  
     * 交易订单查询  
     * @param out_trade_no  
     * @return  
     */  
    public Map<String, Object> tradeQueryForPCWeb(String out_trade_no){  
        AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();  
        JSONObject bizContent = new JSONObject();  
        bizContent.put("trade_no", out_trade_no);  
        request.setBizContent(bizContent.toString());  
        AlipayTradeQueryResponse response = null;  
        try {  
            response = alipayClient.execute(request);  
        } catch (AlipayApiException e) {  
            e.printStackTrace();  
        }  
        Map<String, Object> resultMap = new HashMap<>();  
        resultMap.put("isSuccess", response.isSuccess());  
        if(response.isSuccess()){  
            System.out.println("调用成功");  
            System.out.println(JSON.toJSONString(response));  
            resultMap.put("status", response.getTradeStatus());  
        } else {  
            System.out.println("调用失败");  
            System.out.println(response.getSubMsg());  
            resultMap.put("subMsg", response.getSubMsg());  
        }  
        return resultMap;  
    }  

    /**  
     * 验证签名是否正确  
     * @param sign  
     * @param content  
     * @return  
     */  
    public boolean CheckSignIn(String sign, String content){  
        try {  
            return AlipaySignature.rsaCheck(content, sign, alipayConfig.getPublicKey(), CHART_TYPE, SIGN_TYPE);  
        } catch (AlipayApiException e) {  
            e.printStackTrace();  
        }  
        return false;  
    }  

    /**  
     * 将异步通知的参数转化为Map  
     * @return  
     */  
    public Map<String, String> paramstoMap(HttpServletRequest request) throws UnsupportedEncodingException {  
        Map<String, String> params = new HashMap<String, String>();  
        Map<String, String[]> requestParams = request.getParameterMap();  
        for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {  
            String name = (String) iter.next();  
            String[] values = (String[]) requestParams.get(name);  
            String valueStr = "";  
            for (int i = 0; i < values.length; i++) {  
                valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";  
            }  
            // 乱码解决,这段代码在出现乱码时使用。  
//            valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");  
            params.put(name, valueStr);  
        }  
        return params;  
    }  

}
  • 步骤5,封装config类,用于存放所有的配置属性。
@Data  
@Component  
@ConfigurationProperties(prefix = "alipay")  
public class AlipayConfig {  

    private String gateway;  

    private String appid;  

    private String pid;  

    private String privateKey;  

    private String publicKey;  

    private String returnUrl;  

    private String notifyUrl;  

}

另外需要在application.properties中,准备好上述对应的属性。

# alipay config  
alipay.gateway=https://openapi.alipaydev.com/gateway.do  
alipay.appid=your_appid
alipay.pid=your_pid  
alipay.privatekey=your_private_key
alipay.publickey=your_public_key
alipay.returnurl=完成支付后的同步跳转地址 
alipay.notifyurl=完成支付后,支付宝会异步回调的地址

3. 前端代码实现
前端代码只需要完成两个功能, 1. 根据用户的请求向后端发起支付请求。 2. 直接提交返回数据完成跳转。

下面的例子中,我用typescript实现了用户点击支付之后的功能,

async function onPositiveClick() {  
   paymentLoading.value = true  

   const { data } = await placeAlipayOrder<string>({  
    //你的一些请求参数,例如金额等等
   })  

   const div = document.createElement('divform')  
   div.innerHTML = data  
   document.body.appendChild(div)  
   document.forms[0].setAttribute('target', '_blank')  
   document.forms[0].submit()  

   showModal.value = false  
   paymentLoading.value = false  
}

2.2 创建并上线APP

完成沙盒调试没问题之后,我们需要创建对应的支付宝网页应用并上线。
登录 https://open.alipay.com/develop/manage 并选择创建网页应用。
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/57589dc53ae14dc68694c6ee9bc77dd8.png
填写应用相关信息:
在这里插入图片描述
创建好应用之后,首先在开发设置中,设置好接口加签方式以及应用网关。
在这里插入图片描述
注意密钥选择RSA2,其他按照上面的操作指南一步步走即可,注意保管好自己的私钥和公钥。

之后在产品绑定页,绑定对应的API,比如我们这里是PC网页端支付,找到对应的API绑定就可以了。如果第一次绑定,可能需要填写相关的信息进行审核,按需填写即可,一般审核一天就通过了。

在这里插入图片描述
最后如果一切就绪,我们就可以把APP提交上线了,上线成功之后,我们需要把下面SpringBoot中的properties替换为线上APP的信息,然后就可以在生产环境调用支付宝的接口进行支付了。

# alipay config  
alipay.gateway=https://openapi.alipaydev.com/gateway.do  
alipay.appid=your_appid
alipay.pid=your_pid  
alipay.privatekey=your_private_key
alipay.publickey=your_public_key
alipay.returnurl=完成支付后的同步跳转地址 
alipay.notifyurl=完成支付后,支付宝会异步回调的地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1346546.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux系统使用yum安装MySQL

部署MySQL数据库有多种部署方式&#xff0c;常用的部署方式就有三种&#xff1a;yum安装、rpm安装以及编译安装。每一种安装方式都有自己的优势&#xff0c;那么企业当中通常情况下采用的是rpm和二进制安装的方式。 MySQL官网下载地址 Mysql 5.7的主要特性 更好的性能&#xf…

费曼学习法应用:谈自私和教育的引导

今天这个还是来源于我和九迁的对话&#xff0c;起因是中午吃饭的时候&#xff0c;九迁在学校与班主任老师和数学老师对话中带来的思考。 先听音频&#xff1a; 对话内容&#xff08;以下内容可以边听边看&#xff0c;属于语音转换过来的文字&#xff0c;最后有个总结&#xff0…

中文字符占用字节即相关原理(实现中文(中英混合)字符串的反转)

如有不对欢迎指正。 目录 一.ASCLL字符和中文字符 1.使用无符号数表示的原因(对于中文字符)&#xff1a; 2.但是并不是所有情况都是用无符号数(以下目前只是猜测,如有问题欢迎指正) &#xff1a; 1. 什么时候使用无符号数表示: 2. 不需要使用的情况&#xff1a; …

46、激活函数 - Relu 激活

本节介绍一个在神经网络中非常常见的激活函数 - Relu 激活函数。 什么是ReLU激活函数 ReLU 英文名为 Rectified Linear Unit,又称修正线性单元,是一种简单但很有效的激活函数,它的定义如下: 即当输入 x 大于零时,输出等于他自己;当输入小于等于零时,输出为零,下面是re…

2023年成都市中等职业学校学生技能大赛“网络搭建及应用”赛项竞赛样卷

2023年成都市中等职业学校学生技能大赛 “网络搭建及应用”赛项竞赛样卷 &#xff08;总分1000分&#xff09; 目录 2023年成都市中等职业学校学生技能大赛 “网络搭建及应用”赛项竞赛样卷 网络建设与调试项目&#xff08;500分&#xff09; 服务器搭建与运维项目&#xff08;…

2023年度业务风险报告:四个新风险趋势

目录 倒票的黄牛愈加疯狂 暴增的恶意网络爬虫 愈加猖獗的羊毛党 层出不穷的新风险 业务风险呈现四个趋势 防御云业务安全情报中心“2023年业务风险数据”统计显示&#xff0c;恶意爬虫风险最多&#xff0c;占总数的37.8%&#xff1b;其次是虚假账号注册&#xff0c;占18.79%&am…

负载均衡之LVS

LVS LVS 原理 IPVS LVS 的 IP 负载均衡技术是通过 IPVS 模块来实现的&#xff0c;IPVS 是 LVS 集群系统的核心软件&#xff0c;它的主要作用是&#xff1a;安装在 Director Server 上&#xff0c;同时在 Director Server 上虚拟出一个 IP 地址&#xff0c;用户必须通过这个虚…

【web安全】短信等各类验证码的绕过思路整理

前言 本文是对一些验证码可能出现的问题的总结。 验证码的种类分析 首先验证码有两种&#xff1a; 1.短信验证码&#xff0c;这种通常出现在一些登录&#xff0c;修改绑定信息等位置处。 2.人机验证码&#xff0c;这种一般是用来防止机器操作和密码爆破的&#xff0c;通常…

每日一练(编程题-C/C++)

目录 CSDN每日一练1. 2023/2/27- 一维数组的最大子数组和(类型&#xff1a;数组 难度&#xff1a;中等)2. 2023/4/7 - 小艺照镜子(类型&#xff1a;字符串 难度&#xff1a;困难)3. 2023/4/14 - 最近的回文数(难度&#xff1a;中等)4. 2023/2/1-蛇形矩阵(难度&#xff1a;困难)…

数据采集来源有哪些?怎么做?

数据采集 数据采集&#xff0c;又称数据获取&#xff0c;是指从传感器和其他待测设备等模拟和数字被测单元中自动采集非电量或者电量信号&#xff0c;送到上机中进行分析、处理。 ✦ 一、电商数据采集主要来源 1、互联网公开数据 互联网是数据采集的主要来源之一&#xff0c…

kivy开发一个登陆界面

Kivy Kivy是一个用于开发跨平台移动应用&#xff08;如Android和iOS&#xff09;以及桌面应用&#xff08;如Windows、Linux和macOS&#xff09;的Python框架。它采用开源许可证&#xff08;MIT许可证&#xff09;&#xff0c;提供了丰富的图形界面组件和工具&#xff0c;以便…

Android Studio如何查找和替换

目录 前言 一、概述 二、总结 三、更多资源 前言 在Android Studio中&#xff0c;查找和替换是非常常见的操作&#xff0c;它可以帮助我们快速地定位和修改代码中的错误或不合适的内容。本文将介绍如何在Android Studio中进行查找和替换操作&#xff0c;包括基本的查找和替…

css 设置鼠标覆盖显示菜单

鼠标覆盖到“全部分类”效果如下 鼠标放到“精品推荐”效果如下 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"&g…

Linux网络编程学习心得.2

1.连接服务器 连接服务器 #include <sys/socket.h> int connect(intsockfd , const struct sockaddr *addr, socklen_t addrlen); 功能: 连接服务器 sockfd: socket套接字 addr: ipv4套接字结构体的地址 addrlen: ipv4套接字结构体的长度 2.tcp服务器通信流程 监…

C++初阶——基础知识(函数重载与引用)

目录 1.命名冲突 2.命名空间 3.缺省参数 4.函数重载 1.函数重载的特点包括&#xff1a; 2.函数重载的好处包括&#xff1a; 3.引用 引用的特点包括 引用的主要用途包括 引用和指针 引用 指针 类域 命名空间域 局部域 全局域 第一个关键字 命名冲突 同一个项目之间冲…

8天狂收6000+⭐️,可商用的开源Stream Diffusion

加州大学伯克利分校、东洋大学、东京工业大学、麻省理工学院和筑波大学等研究人员&#xff0c;联合开源了一款创新性实时交互图像生成框架——Stream Diffusion。 Stream Diffusion的技术创新点在于&#xff0c;将传统的顺序去噪变成流批处理去噪&#xff0c;消除了漫长的等待…

嵌入式学习路线

嵌入式系统是一种将软件和硬件紧密结合的技术&#xff0c;首先我们要认识到&#xff0c;无论我们是专注于软件开发还是硬件开发&#xff0c;最终的目标都是为了更好的工作和职业发展。 根据企业的规模和需求&#xff0c;大公司更倾向于将职责分得更细&#xff0c;例如软件分为…

2023年总结(2023年1月1日至2023年12月31日)

前言 时间过得真快啊&#xff0c;一年又过去了。 从去年11月换了家公司后&#xff0c;工作就稳定多了&#xff0c;做的工作也是我喜欢做的工作——摄像头驱动&#xff0c;平时也挺轻松的&#xff0c;偶尔有事儿的时候会压力大点&#xff0c;加点班&#xff0c;其他都还好&…

SAP 资产管理后台配置之设定主数据字段

前阵子给财务创建了一个固定资产类型&#xff0c;但同事使用时发现字段跟平时不一样。 正常是有下面这些标签页的 然后我找到主数据屏幕格式的配置里发现 发现格式默认错了 应该是默认我司的自定义格式ZSAP 但是改成ZSAP还是不会生效 需要给这个资产分类重新分配一下字段标签页…

『番外篇八』SwiftUI 脑洞大开实现“另类”视图跟随方法

概览 在 SwiftUI 的开发中,我们时常需要用指尖丝滑般地操作指定视图:比如,我们需要在拖动视图后让它自动归位,或者拖动一个视图时让另一个视图跟随它移动。 我们随后将会详细讨论上述两个 SwiftUI 中与视图移动相关场景的实现。 在本篇博文中,您将学到如下内容: 概览1.…